mirror of
https://github.com/ProjectSegfault/website.git
synced 2024-11-08 17:12:24 +05:30
better navbar
This commit is contained in:
parent
926890b324
commit
3ff1e24e7c
12
.github/workflows/docker-dev.yml
vendored
12
.github/workflows/docker-dev.yml
vendored
@ -3,18 +3,16 @@ name: Docker dev
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'dev'
|
- "dev"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: 'Build'
|
name: "Build"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Set up QEMU
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: "Build:checkout"
|
- name: "Build:checkout"
|
||||||
@ -27,7 +25,7 @@ jobs:
|
|||||||
username: ProjectSegfault
|
username: ProjectSegfault
|
||||||
password: ${{ secrets.ACCESS_TOKEN }}
|
password: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: 'Build:dockerimage'
|
- name: "Build:dockerimage"
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
tags: ghcr.io/projectsegfault/website:dev
|
tags: ghcr.io/projectsegfault/website:dev
|
||||||
|
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
@ -3,18 +3,16 @@ name: Docker
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- "master"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: 'Build'
|
name: "Build"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Set up QEMU
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: "Build:checkout"
|
- name: "Build:checkout"
|
||||||
@ -27,7 +25,7 @@ jobs:
|
|||||||
username: ProjectSegfault
|
username: ProjectSegfault
|
||||||
password: ${{ secrets.ACCESS_TOKEN }}
|
password: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: 'Build:dockerimage'
|
- name: "Build:dockerimage"
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
tags: ghcr.io/projectsegfault/website:latest
|
tags: ghcr.io/projectsegfault/website:latest
|
||||||
|
@ -38,7 +38,7 @@ If you want to run the website locally in production follow the steps in [develo
|
|||||||
The website has the following **mandatory** environment variables
|
The website has the following **mandatory** environment variables
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|:------------------ |:------------------------- |
|
| :----------------- | :----------------------------------|
|
||||||
| AUTH_CLIENT_ID | Authentik client ID |
|
| AUTH_CLIENT_ID | Authentik client ID |
|
||||||
| AUTH_CLIENT_SECRET | Authentik client secret |
|
| AUTH_CLIENT_SECRET | Authentik client secret |
|
||||||
| AUTH_ISSUER | Authentication issuer URL |
|
| AUTH_ISSUER | Authentication issuer URL |
|
||||||
@ -46,4 +46,5 @@ The website has the following **mandatory** environment variables
|
|||||||
| AUTH_SECRET | Random 32 char secret |
|
| AUTH_SECRET | Random 32 char secret |
|
||||||
| GHOST_URL | Your Ghost CMS URL |
|
| GHOST_URL | Your Ghost CMS URL |
|
||||||
| GHOST_API_KEY | Your Ghost CMS API key |
|
| GHOST_API_KEY | Your Ghost CMS API key |
|
||||||
|
| KUMA_URL | Your Uptime Kuma announcements URL |
|
||||||
| ORIGIN | Your domain |
|
| ORIGIN | Your domain |
|
@ -4,7 +4,8 @@
|
|||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, html.light {
|
html,
|
||||||
|
html.light {
|
||||||
--accent: #00a584;
|
--accent: #00a584;
|
||||||
--accent-translucent: #00a58498;
|
--accent-translucent: #00a58498;
|
||||||
--font-primary: "JetBrains Mono", monospace;
|
--font-primary: "JetBrains Mono", monospace;
|
||||||
|
10
src/app.html
10
src/app.html
@ -2,8 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
|
<link
|
||||||
<meta name="viewport" content="width=device-width" />
|
rel="icon"
|
||||||
|
href="%sveltekit.assets%/logo.svg"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width"
|
||||||
|
/>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
@ -6,7 +6,14 @@ import type { Profile } from "@auth/core/types";
|
|||||||
import { redirect, type Handle } from "@sveltejs/kit";
|
import { redirect, type Handle } from "@sveltejs/kit";
|
||||||
import { sequence } from "@sveltejs/kit/hooks";
|
import { sequence } from "@sveltejs/kit/hooks";
|
||||||
|
|
||||||
const hasAuth = !env.AUTH_CLIENT_ID || !env.AUTH_CLIENT_SECRET || !env.AUTH_ISSUER || !env.AUTH_TRUST_HOST || !env.AUTH_SECRET ? false : true;
|
const hasAuth =
|
||||||
|
!env.AUTH_CLIENT_ID ||
|
||||||
|
!env.AUTH_CLIENT_SECRET ||
|
||||||
|
!env.AUTH_ISSUER ||
|
||||||
|
!env.AUTH_TRUST_HOST ||
|
||||||
|
!env.AUTH_SECRET
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
export const handle: Handle = sequence(
|
export const handle: Handle = sequence(
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@ -19,7 +26,8 @@ export const handle: Handle = sequence(
|
|||||||
}) as Provider<Profile>
|
}) as Provider<Profile>
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
hasAuth ? async ({ event, resolve }) => {
|
hasAuth
|
||||||
|
? async ({ event, resolve }) => {
|
||||||
if (event.url.pathname.startsWith("/admin")) {
|
if (event.url.pathname.startsWith("/admin")) {
|
||||||
const session = await event.locals.getSession();
|
const session = await event.locals.getSession();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@ -31,7 +39,8 @@ export const handle: Handle = sequence(
|
|||||||
transformPageChunk: ({ html }) => html
|
transformPageChunk: ({ html }) => html
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} : async ({ event, resolve }) => {
|
}
|
||||||
|
: async ({ event, resolve }) => {
|
||||||
if (event.url.pathname.startsWith("/admin")) {
|
if (event.url.pathname.startsWith("/admin")) {
|
||||||
throw redirect(303, "/login");
|
throw redirect(303, "/login");
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,38 @@
|
|||||||
export let isPost: boolean = false;
|
export let isPost: boolean = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 flex-1 {isPost ? "nav:(flex-row gap-4)" : ""}">
|
<div class="flex flex-col gap-2 flex-1 {isPost ? 'nav:(flex-row gap-4)' : ''}">
|
||||||
{#if post.tags.length > 0}
|
{#if post.tags.length > 0}
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<div class="i-ic:outline-bookmarks text-xl -ml-1" />
|
<div class="i-ic:outline-bookmarks text-xl -ml-1" />
|
||||||
{#each post.tags as tag}
|
{#each post.tags as tag}
|
||||||
<a href="/blog/tags/{tag.slug}" class="no-underline rounded p-1 {isPost ? "bg-secondary" : "bg-primary"}">{tag.name}</a>
|
<a
|
||||||
|
href="/blog/tags/{tag.slug}"
|
||||||
|
class="no-underline rounded p-1 {isPost
|
||||||
|
? 'bg-secondary'
|
||||||
|
: 'bg-primary'}">{tag.name}</a
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each post.authors as author}
|
{#each post.authors as author}
|
||||||
<a href="/blog/authors/{author.slug}" class="flex items-center gap-2 no-underline"><div class="i-ic:outline-person text-xl -ml-1" />{author.name}</a>
|
<a
|
||||||
|
href="/blog/authors/{author.slug}"
|
||||||
|
class="flex items-center gap-2 no-underline"
|
||||||
|
><div class="i-ic:outline-person text-xl -ml-1" />
|
||||||
|
{author.name}</a
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
<span class="flex items-center gap-2"><div class="i-ic:outline-calendar-month text-xl -ml-1" /> {dayjs
|
<span class="flex items-center gap-2"
|
||||||
(post.published_at)
|
><div class="i-ic:outline-calendar-month text-xl -ml-1" />
|
||||||
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
{dayjs(post.published_at).format("ddd, DD MMM YYYY HH:mm")}</span
|
||||||
<span class="flex items-center gap-2"><div class="i-ic:outline-edit text-2xl -ml-1" /> {post.plaintext.trim().split(/\s+/).length} words</span>
|
>
|
||||||
<span class="flex items-center gap-2"><div class="i-ic:outline-import-contacts text-xl -ml-1" /> {post.reading_time} minute read</span>
|
<span class="flex items-center gap-2"
|
||||||
|
><div class="i-ic:outline-edit text-2xl -ml-1" />
|
||||||
|
{post.plaintext.trim().split(/\s+/).length} words</span
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2"
|
||||||
|
><div class="i-ic:outline-import-contacts text-xl -ml-1" />
|
||||||
|
{post.reading_time} minute read</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
@ -3,6 +3,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="prose flex flex-col text-justify m-auto">
|
<div class="prose flex flex-col text-justify m-auto">
|
||||||
<img src={data.post.feature_image} alt="{data.post.title} image" class="rounded">
|
<img
|
||||||
|
src={data.post.feature_image}
|
||||||
|
alt="{data.post.title} image"
|
||||||
|
class="rounded"
|
||||||
|
/>
|
||||||
{@html data.post.html}
|
{@html data.post.html}
|
||||||
</div>
|
</div>
|
@ -3,10 +3,15 @@
|
|||||||
export let isPost: boolean = false;
|
export let isPost: boolean = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 p-4 rounded {isPost ? "" : "w-110 bg-secondary"}">
|
<div
|
||||||
|
class="flex flex-col gap-4 p-4 rounded {isPost ? '' : 'w-110 bg-secondary'}"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
{#if url}
|
{#if url}
|
||||||
<a href={url} class="text-center">View on Ghost</a>
|
<a
|
||||||
|
href={url}
|
||||||
|
class="text-center">View on Ghost</a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
@ -3,9 +3,11 @@
|
|||||||
export let name: string;
|
export let name: string;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
<a href="/blog/{name}/{item.slug}" class="bg-secondary sm:w-md p-2 rounded no-underline">{item.name}</a>
|
<a
|
||||||
|
href="/blog/{name}/{item.slug}"
|
||||||
|
class="bg-secondary sm:w-md p-2 rounded no-underline">{item.name}</a
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
@ -5,10 +5,17 @@
|
|||||||
|
|
||||||
{#if !isPost}
|
{#if !isPost}
|
||||||
{#if post.feature_image}
|
{#if post.feature_image}
|
||||||
<img src={post.feature_image} alt="{post.title} image" class="rounded">
|
<img
|
||||||
|
src={post.feature_image}
|
||||||
|
alt="{post.title} image"
|
||||||
|
class="rounded"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/blog/{post.slug}" class="text-text no-underline hover:underline"><span class="text-xl font-bold">{post.title}</span></a>
|
<a
|
||||||
|
href="/blog/{post.slug}"
|
||||||
|
class="text-text no-underline hover:underline"
|
||||||
|
><span class="text-xl font-bold">{post.title}</span></a
|
||||||
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-xl font-bold">{post.title}</span>
|
<span class="text-xl font-bold">{post.title}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<footer class="sticky top-full">
|
<footer class="sticky top-full">
|
||||||
<div class="flex flex-col justify-center sm:flex-row gap-1 border-t border-t-solid border-t-grey p-3 text-sm">
|
<div
|
||||||
<p class="flex flex-row items-center gap-1">Made with <i class="i-simple-icons:svelte text-[#FF3E00] block" /> SvelteKit</p>
|
class="flex flex-col justify-center sm:flex-row gap-1 border-t border-t-solid border-t-grey p-3 text-sm"
|
||||||
|
>
|
||||||
|
<p class="flex flex-row items-center gap-1">
|
||||||
|
Made with <i class="i-simple-icons:svelte text-[#FF3E00] block" /> SvelteKit
|
||||||
|
</p>
|
||||||
<span class="hidden sm:block">-</span>
|
<span class="hidden sm:block">-</span>
|
||||||
<a href="https://github.com/ProjectSegfault/website">Source code</a>
|
<a href="https://github.com/ProjectSegfault/website">Source code</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
<div class="flex flex-col gap-6 items-center text-center mt-[7%]">
|
<div class="flex flex-col gap-6 items-center text-center mt-[7%]">
|
||||||
<h1 class="text-5xl font-extrabold text-accent my-0 border-b-0 pb-0">Project Segfault</h1>
|
<h1 class="text-5xl font-extrabold text-accent my-0 border-b-0 pb-0">
|
||||||
|
Project Segfault
|
||||||
|
</h1>
|
||||||
<p class="text-2xl">Open source development and hosted services.</p>
|
<p class="text-2xl">Open source development and hosted services.</p>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<a href="/instances" class="button"><div class="i-ic:outline-computer" /> Instances</a>
|
<a
|
||||||
<a href="/donate" class="button !bg-amber !text-black"><div class="i-ic:outline-attach-money" /> Donate</a>
|
href="/instances"
|
||||||
|
class="button"
|
||||||
|
><div class="i-ic:outline-computer" />
|
||||||
|
Instances</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/donate"
|
||||||
|
class="button !bg-amber !text-black"
|
||||||
|
><div class="i-ic:outline-attach-money" />
|
||||||
|
Donate</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ThemeToggle from "./ThemeToggle.svelte";
|
import ThemeToggle from "$lib/Nav/ThemeToggle.svelte";
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import { slide } from "svelte/transition";
|
import { slide } from "svelte/transition"
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from "svelte/easing";
|
||||||
|
|
||||||
$: currentPage = $page.url.pathname;
|
$: currentPage = $page.url.pathname;
|
||||||
|
|
||||||
@ -14,15 +14,16 @@
|
|||||||
|
|
||||||
$: showMenuButton = hasJS && isMobile;
|
$: showMenuButton = hasJS && isMobile;
|
||||||
|
|
||||||
$: menuOpen = !hasJS || hasJS && !isMobile;
|
$: menuOpen = !hasJS || (hasJS && !isMobile);
|
||||||
|
|
||||||
$: menuOpenMobile = isMobile && menuOpen;
|
$: menuOpenMobile = isMobile && menuOpen;
|
||||||
|
|
||||||
$: showThemeToggle = hasJS;
|
$: showThemeToggle = hasJS;
|
||||||
|
|
||||||
const toggleMenu = () => menuOpen = !menuOpen;
|
const toggleMenu = () => (menuOpen = !menuOpen);
|
||||||
|
|
||||||
const handleNavigation = () => showMenuButton ? menuOpen = false : menuOpen = true;
|
const handleNavigation = () =>
|
||||||
|
showMenuButton ? (menuOpen = false) : (menuOpen = true);
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
{ name: "Instances", url: "/instances" },
|
{ name: "Instances", url: "/instances" },
|
||||||
@ -48,13 +49,17 @@
|
|||||||
<svelte:window bind:innerWidth />
|
<svelte:window bind:innerWidth />
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="bg-primary {menuOpenMobile ? "border-none" : "border-b border-b-solid border-b-grey"} {isMobile ? "py-2" : ""} flex px-2 flex-col justify-between nav:(flex-row items-center)"
|
class="bg-primary {menuOpenMobile
|
||||||
class:hasJSNav={hasJS}
|
? 'border-none'
|
||||||
class:noJSNav={!hasJS}
|
: 'border-b border-b-solid border-b-grey'} {isMobile
|
||||||
|
? 'py-2'
|
||||||
|
: ''} flex px-2 flex-col justify-between nav:(flex-row items-center) {hasJS
|
||||||
|
? 'sticky top-0 z-50'
|
||||||
|
: 'border-b border-b-solid border-b-grey'}"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<a
|
<a
|
||||||
class="flex items-center decoration-none text-text gap-2 transition-opacity duration-250 hover:opacity-80"
|
class="flex items-center decoration-none text-text gap-2 transition-filter duration-250"
|
||||||
href="/"
|
href="/"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -68,17 +73,21 @@
|
|||||||
{#if showMenuButton}
|
{#if showMenuButton}
|
||||||
<button
|
<button
|
||||||
on:click={toggleMenu}
|
on:click={toggleMenu}
|
||||||
class="{menuOpen ? "i-ic:outline-close" : "i-ic:outline-menu"} h-4 w-4 cursor-pointer mr-2"
|
class="{menuOpen
|
||||||
|
? 'i-ic:outline-close'
|
||||||
|
: 'i-ic:outline-menu'} h-4 w-4 cursor-pointer mr-2"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if menuOpen}
|
{#if menuOpen}
|
||||||
<div
|
<div
|
||||||
class="links {isMobile ? "!children:py-2" : ""}"
|
class="links flex flex-row gap-2 {isMobile
|
||||||
class:hasJS={hasJS}
|
? '!children:py-2'
|
||||||
class:noJS={!hasJS}
|
: ''} {hasJS
|
||||||
transition:slide="{{duration: 300, easing: quintOut }}"
|
? 'lt-nav:(flex flex-col pt-2 gap-2 fixed bg-primary w-full left-0 top-[2.75rem] p-2 z-50 border-b-solid border-b border-b-grey shadow shadow-secondary)'
|
||||||
|
: 'lt-nav:(grid grid-cols-2 gap-2 pt-2 w-fit)'}"
|
||||||
|
transition:slide={{ duration: 300, easing: quintOut }}
|
||||||
>
|
>
|
||||||
{#each menus as { url, name, external }}
|
{#each menus as { url, name, external }}
|
||||||
<a
|
<a
|
||||||
@ -88,9 +97,7 @@
|
|||||||
href={url}
|
href={url}
|
||||||
on:click={handleNavigation}
|
on:click={handleNavigation}
|
||||||
>{#if external}
|
>{#if external}
|
||||||
<div
|
<div class="i-ic:outline-open-in-new mr-2 h-4 w-4" />
|
||||||
class="i-ic:outline-open-in-new mr-2 h-4 w-4"
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
@ -124,52 +131,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.links > * {
|
.links > * {
|
||||||
@apply py-4 px-2 cursor-pointer text-text decoration-none transition-color duration-250 text-sm font-500 flex items-center hover\:(text-accent);
|
@apply py-4 px-2 text-text decoration-none transition-filter duration-250 text-sm flex items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon > span {
|
.icon > span {
|
||||||
@apply flex text-sm nav\:hidden;
|
@apply text-sm nav\:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@apply flex flex-row items-center gap-2 text-base;
|
@apply flex items-center gap-2 text-base;
|
||||||
}
|
|
||||||
|
|
||||||
.hasJS {
|
|
||||||
@apply flex flex-col pt-2 gap-2 fixed bg-primary w-full left-0 top-[2.75rem] p-2 z-50 border-b-solid border-b border-b-grey shadow shadow-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noJS {
|
|
||||||
@apply grid grid-cols-2 gap-2 pt-2 w-fit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hasJSNav {
|
|
||||||
@apply sticky top-0 z-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noJSNav {
|
|
||||||
@apply border-b border-b-solid border-b-grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1090px) {
|
|
||||||
.hasJS {
|
|
||||||
flex-direction: row;
|
|
||||||
padding-top: 0;
|
|
||||||
position: initial;
|
|
||||||
background-color: initial;
|
|
||||||
width: initial;
|
|
||||||
left: initial;
|
|
||||||
top: initial;
|
|
||||||
padding: initial;
|
|
||||||
z-index: initial;
|
|
||||||
border-bottom: initial;
|
|
||||||
box-shadow: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noJS {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
on:click={toggle}
|
on:click={toggle}
|
||||||
class="text-text flex items-center text-sm"
|
class="text-text flex items-center text-sm"
|
||||||
>
|
>
|
||||||
<div class="i-ic:{theme === 'dark' ? 'outline-light-mode' : 'outline-dark-mode'} h-4 w-4" />
|
<div
|
||||||
|
class="i-ic:{theme === 'dark'
|
||||||
|
? 'outline-light-mode'
|
||||||
|
: 'outline-dark-mode'} h-4 w-4"
|
||||||
|
/>
|
||||||
<span class="ml-2 nav:(hidden ml-1)">Toggle theme</span>
|
<span class="ml-2 nav:(hidden ml-1)">Toggle theme</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key pathname}
|
{#key pathname}
|
||||||
<div in:fly={{ x: -10, duration: 250, delay: 250 }} out:fly={{ x: 5, duration: 250 }}>
|
<div
|
||||||
|
in:fly={{ x: -10, duration: 250, delay: 250 }}
|
||||||
|
out:fly={{ x: 5, duration: 250 }}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from "$app/stores";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
|
@ -13,18 +13,27 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$page.data.title} | Project Segfault {$page.url.pathname.startsWith("/blog") ? "blog" : ""}</title>
|
<title
|
||||||
|
>{$page.data.title} | Project Segfault {$page.url.pathname.startsWith(
|
||||||
|
"/blog"
|
||||||
|
)
|
||||||
|
? "blog"
|
||||||
|
: ""}</title
|
||||||
|
>
|
||||||
{#if $page.data.description}
|
{#if $page.data.description}
|
||||||
<meta name="description" content={$page.data.description} />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={$page.data.description}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|
||||||
<main class="px-8 mb-8 max-w-90rem m-auto">
|
<main class="px-8 mb-8 max-w-90rem m-auto">
|
||||||
<PageTransition pathname={data.pathname}>
|
<PageTransition pathname={data.pathname}>
|
||||||
<slot />
|
<slot />
|
||||||
</PageTransition>
|
</PageTransition>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
|
@ -3,5 +3,5 @@ import type { LayoutLoad } from "./$types";
|
|||||||
export const load = (async ({ url: { pathname } }) => {
|
export const load = (async ({ url: { pathname } }) => {
|
||||||
return {
|
return {
|
||||||
pathname
|
pathname
|
||||||
}
|
};
|
||||||
}) satisfies LayoutLoad
|
}) satisfies LayoutLoad;
|
||||||
|
@ -11,7 +11,7 @@ export const load = (async () => {
|
|||||||
const meta = {
|
const meta = {
|
||||||
title: "Home",
|
title: "Home",
|
||||||
description: "Open source development and hosted services."
|
description: "Open source development and hosted services."
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios(env.KUMA_URL, { httpsAgent: agent });
|
const res = await axios(env.KUMA_URL, { httpsAgent: agent });
|
||||||
|
@ -31,18 +31,36 @@
|
|||||||
{#if !data.error}
|
{#if !data.error}
|
||||||
{#if data.announcements.incident}
|
{#if data.announcements.incident}
|
||||||
<div class="flex flex-col items-center mt-16">
|
<div class="flex flex-col items-center mt-16">
|
||||||
<div class="flex flex-col prose break-words rounded p-4 lt-sm:max-w-74 sm:(p-8) {backgroundColor === "#212529" ? "text-text" : "text-black"}" style="background-color: {backgroundColor};">
|
<div
|
||||||
|
class="flex flex-col prose break-words rounded p-4 lt-sm:max-w-74 sm:(p-8) {backgroundColor ===
|
||||||
|
'#212529'
|
||||||
|
? 'text-text'
|
||||||
|
: 'text-black'}"
|
||||||
|
style="background-color: {backgroundColor};"
|
||||||
|
>
|
||||||
{#if data.announcements.incident.title}
|
{#if data.announcements.incident.title}
|
||||||
<span class="text-xl font-semibold">{data.announcements.incident.title}</span>
|
<span class="text-xl font-semibold"
|
||||||
|
>{data.announcements.incident.title}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if data.announcements.incident.content}
|
{#if data.announcements.incident.content}
|
||||||
<p>{@html sanitizeHtml(data.announcements.incident.content.replace(/\n/g, "<br />"))}</p>
|
<p>
|
||||||
|
{@html sanitizeHtml(
|
||||||
|
data.announcements.incident.content.replace(
|
||||||
|
/\n/g,
|
||||||
|
"<br />"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span>Created - {data.announcements.incident.createdDate}</span>
|
<span>Created - {data.announcements.incident.createdDate}</span>
|
||||||
{#if data.announcements.incident.lastUpdatedDate}
|
{#if data.announcements.incident.lastUpdatedDate}
|
||||||
<span>Updated - {data.announcements.incident.lastUpdatedDate}</span>
|
<span
|
||||||
|
>Updated - {data.announcements.incident
|
||||||
|
.lastUpdatedDate}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@ import fetchGhost from "./fetchGhost";
|
|||||||
export const load = (async ({ fetch }) => {
|
export const load = (async ({ fetch }) => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Blog"
|
title: "Blog"
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts: fetchGhost("posts"),
|
posts: fetchGhost("posts"),
|
||||||
|
@ -2,14 +2,35 @@
|
|||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
import { PostsContainer, PostOuter, Title, Meta, ReadMore } from "$lib/BlogCard";
|
import {
|
||||||
|
PostsContainer,
|
||||||
|
PostOuter,
|
||||||
|
Title,
|
||||||
|
Meta,
|
||||||
|
ReadMore
|
||||||
|
} from "$lib/BlogCard";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4">
|
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4">
|
||||||
<span class="text-4xl font-bold">{data.title}</span>
|
<span class="text-4xl font-bold">{data.title}</span>
|
||||||
<a href="/blog/tags" class="button sm:w-fit"><div class="i-ic:outline-bookmarks" /> Tags</a>
|
<a
|
||||||
<a href="/blog/authors" class="button sm:w-fit"><div class="i-ic:outline-people text-xl" /> Authors</a>
|
href="/blog/tags"
|
||||||
<a href="https://blog.projectsegfau.lt/rss" class="button sm:w-fit !bg-[#ee802f]"><div class="i-simple-icons:rss" /> RSS</a>
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-bookmarks" />
|
||||||
|
Tags</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/blog/authors"
|
||||||
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-people text-xl" />
|
||||||
|
Authors</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://blog.projectsegfau.lt/rss"
|
||||||
|
class="button sm:w-fit !bg-[#ee802f]"
|
||||||
|
><div class="i-simple-icons:rss" />
|
||||||
|
RSS</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !data.posts.error}
|
{#if !data.posts.error}
|
||||||
|
@ -8,7 +8,7 @@ export const load = (async ({ params, fetch }) => {
|
|||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: !allPosts.error ? data.posts[0].title : ""
|
title: !allPosts.error ? data.posts[0].title : ""
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
post: !allPosts.error ? data.posts[0] : {},
|
post: !allPosts.error ? data.posts[0] : {},
|
||||||
|
@ -1,19 +1,39 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PostOuter, Title, Meta, PostContent, ReadMore, PostsContainer } from "$lib/BlogCard";
|
import {
|
||||||
|
PostOuter,
|
||||||
|
Title,
|
||||||
|
Meta,
|
||||||
|
PostContent,
|
||||||
|
ReadMore,
|
||||||
|
PostsContainer
|
||||||
|
} from "$lib/BlogCard";
|
||||||
|
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
$: index = !data.allPosts.error ? data.allPosts.posts.findIndex((post: { slug: string; }) => post.slug === data.post.slug) : null;
|
$: index = !data.allPosts.error
|
||||||
|
? data.allPosts.posts.findIndex(
|
||||||
|
(post: { slug: string }) => post.slug === data.post.slug
|
||||||
|
)
|
||||||
|
: null;
|
||||||
$: next = !data.allPosts.error ? data.allPosts.posts[index - 1] : null;
|
$: next = !data.allPosts.error ? data.allPosts.posts[index - 1] : null;
|
||||||
$: previous = !data.allPosts.error ? data.allPosts.posts[index + 1] : null;
|
$: previous = !data.allPosts.error ? data.allPosts.posts[index + 1] : null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !data.allPosts.error}
|
{#if !data.allPosts.error}
|
||||||
<PostOuter url={data.post.url} isPost>
|
<PostOuter
|
||||||
|
url={data.post.url}
|
||||||
|
isPost
|
||||||
|
>
|
||||||
<div class="text-center mt-4 flex flex-col items-center gap-4">
|
<div class="text-center mt-4 flex flex-col items-center gap-4">
|
||||||
<Title post={data.post} isPost />
|
<Title
|
||||||
<Meta post={data.post} isPost />
|
post={data.post}
|
||||||
|
isPost
|
||||||
|
/>
|
||||||
|
<Meta
|
||||||
|
post={data.post}
|
||||||
|
isPost
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<PostContent {data} />
|
<PostContent {data} />
|
||||||
</PostOuter>
|
</PostOuter>
|
||||||
|
@ -6,7 +6,7 @@ export const load = (async ({ fetch }) => {
|
|||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Blog authors"
|
title: "Blog authors"
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authors: data,
|
authors: data,
|
||||||
|
@ -8,7 +8,10 @@
|
|||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
|
|
||||||
{#if !data.authors.error}
|
{#if !data.authors.error}
|
||||||
<SingleWordLists items={data.authors.authors} name="authors" />
|
<SingleWordLists
|
||||||
|
items={data.authors.authors}
|
||||||
|
name="authors"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<p>{data.authors.message}</p>
|
<p>{data.authors.message}</p>
|
||||||
{/if}
|
{/if}
|
@ -4,23 +4,24 @@ import fetchGhost from "../../fetchGhost";
|
|||||||
export const load = (async ({ params, fetch }) => {
|
export const load = (async ({ params, fetch }) => {
|
||||||
const data = await fetchGhost("posts", "&filter=author:" + params.author);
|
const data = await fetchGhost("posts", "&filter=author:" + params.author);
|
||||||
|
|
||||||
const authorsLoop = !data.error ? data.posts[0].authors.map((author: { slug: string; name: any; }) => {
|
const authorsLoop = !data.error
|
||||||
|
? data.posts[0].authors.map((author: { slug: string; name: any }) => {
|
||||||
if (author.slug === params.author) {
|
if (author.slug === params.author) {
|
||||||
return author.name;
|
return author.name;
|
||||||
}
|
}
|
||||||
}) : [];
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const authorName = authorsLoop.filter((tag: any) => tag !== undefined)[0];
|
const authorName = authorsLoop.filter((tag: any) => tag !== undefined)[0];
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Blog author " + authorName,
|
title: "Blog author " + authorName,
|
||||||
description: "Blog posts by " + authorName,
|
description: "Blog posts by " + authorName
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts: data,
|
posts: data,
|
||||||
authorName,
|
authorName,
|
||||||
...meta,
|
...meta
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
import { PostsContainer, PostOuter, Title, Meta, ReadMore } from "$lib/BlogCard";
|
import {
|
||||||
|
PostsContainer,
|
||||||
|
PostOuter,
|
||||||
|
Title,
|
||||||
|
Meta,
|
||||||
|
ReadMore
|
||||||
|
} from "$lib/BlogCard";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>Blog author <span class="text-accent">{data.authorName}</span></h1>
|
<h1>Blog author <span class="text-accent">{data.authorName}</span></h1>
|
||||||
|
@ -6,9 +6,18 @@ const agent = new Agent({
|
|||||||
family: 4
|
family: 4
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchGhost = async (action: string, additional?: string ) => {
|
const fetchGhost = async (action: string, additional?: string) => {
|
||||||
try {
|
try {
|
||||||
const request = await axios(env.GHOST_URL + "/ghost/api/content/" + action + "/?key=" + env.GHOST_API_KEY + "&include=authors,tags&limit=all&formats=html,plaintext" + (additional ? additional : ""), { httpsAgent: agent });
|
const request = await axios(
|
||||||
|
env.GHOST_URL +
|
||||||
|
"/ghost/api/content/" +
|
||||||
|
action +
|
||||||
|
"/?key=" +
|
||||||
|
env.GHOST_API_KEY +
|
||||||
|
"&include=authors,tags&limit=all&formats=html,plaintext" +
|
||||||
|
(additional ? additional : ""),
|
||||||
|
{ httpsAgent: agent }
|
||||||
|
);
|
||||||
|
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
return request.data;
|
return request.data;
|
||||||
@ -18,6 +27,6 @@ const fetchGhost = async (action: string, additional?: string ) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { error: true, message: "Error: " + err };
|
return { error: true, message: "Error: " + err };
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default fetchGhost;
|
export default fetchGhost;
|
@ -6,10 +6,10 @@ export const load = (async () => {
|
|||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Blog tags"
|
title: "Blog tags"
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tags: data,
|
tags: data,
|
||||||
...meta
|
...meta
|
||||||
}
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
@ -8,7 +8,10 @@
|
|||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
|
|
||||||
{#if !data.tags.error}
|
{#if !data.tags.error}
|
||||||
<SingleWordLists items={data.tags.tags} name="tags" />
|
<SingleWordLists
|
||||||
|
items={data.tags.tags}
|
||||||
|
name="tags"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<p>{data.tags.message}</p>
|
<p>{data.tags.message}</p>
|
||||||
{/if}
|
{/if}
|
@ -4,21 +4,23 @@ import fetchGhost from "../../fetchGhost";
|
|||||||
export const load = (async ({ params, fetch }) => {
|
export const load = (async ({ params, fetch }) => {
|
||||||
const data = await fetchGhost("posts", "&filter=tags:" + params.tag);
|
const data = await fetchGhost("posts", "&filter=tags:" + params.tag);
|
||||||
|
|
||||||
const tagsLoop = !data.error ? data.posts[0].tags.map((tag: { slug: string; name: any; }) => {
|
const tagsLoop = !data.error
|
||||||
|
? data.posts[0].tags.map((tag: { slug: string; name: any }) => {
|
||||||
if (tag.slug === params.tag) {
|
if (tag.slug === params.tag) {
|
||||||
return tag.name;
|
return tag.name;
|
||||||
}
|
}
|
||||||
}) : [];
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const tagName = tagsLoop.filter((tag: any) => tag !== undefined)[0];
|
const tagName = tagsLoop.filter((tag: any) => tag !== undefined)[0];
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Blog tag " + tagName
|
title: "Blog tag " + tagName
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts: data,
|
posts: data,
|
||||||
tagName: tagName,
|
tagName: tagName,
|
||||||
...meta
|
...meta
|
||||||
}
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
@ -2,7 +2,13 @@
|
|||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
import { PostsContainer, PostOuter, Title, Meta, ReadMore } from "$lib/BlogCard";
|
import {
|
||||||
|
PostsContainer,
|
||||||
|
PostOuter,
|
||||||
|
Title,
|
||||||
|
Meta,
|
||||||
|
ReadMore
|
||||||
|
} from "$lib/BlogCard";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>Blog tag <span class="text-accent">{data.tagName}</span></h1>
|
<h1>Blog tag <span class="text-accent">{data.tagName}</span></h1>
|
||||||
|
@ -4,17 +4,33 @@
|
|||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
|
|
||||||
<h2>Matrix</h2>
|
<h2>Matrix</h2>
|
||||||
|
|
||||||
<p>We have a Matrix space for general discussion, support and announcements about Project Segfault over at <a href="https://matrix.to/#/#project-segfault:projectsegfau.lt/">this link</a>.</p>
|
<p>
|
||||||
|
We have a Matrix space for general discussion, support and announcements
|
||||||
|
about Project Segfault over at <a
|
||||||
|
href="https://matrix.to/#/#project-segfault:projectsegfau.lt/"
|
||||||
|
>this link</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Email</h2>
|
<h2>Email</h2>
|
||||||
|
|
||||||
<p>Our primary email address is <a href="mailto:contact@projectsegfau.lt">contact@projectsegfau.lt</a>. You can use this as a way to send feedback or other stuff to us if you don't have or don't want to make a Matrix account. When waiting for us to answer make sure to check your spam, since some email providers block non-popular domains.</p>
|
<p>
|
||||||
|
Our primary email address is <a href="mailto:contact@projectsegfau.lt"
|
||||||
|
>contact@projectsegfau.lt</a
|
||||||
|
>. You can use this as a way to send feedback or other stuff to us if you
|
||||||
|
don't have or don't want to make a Matrix account. When waiting for us to
|
||||||
|
answer make sure to check your spam, since some email providers block
|
||||||
|
non-popular domains.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Members</h2>
|
<h2>Members</h2>
|
||||||
|
|
||||||
<p>You can contact individual members by using the links provided in <a href="/team">the team page</a>.</p>
|
<p>
|
||||||
|
You can contact individual members by using the links provided in <a
|
||||||
|
href="/team">the team page</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CryptoInfo from "./CryptoInfo.svelte";
|
import CryptoInfo from "./CryptoInfo.svelte";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
@ -10,26 +9,55 @@
|
|||||||
|
|
||||||
<h2>What we do with donations</h2>
|
<h2>What we do with donations</h2>
|
||||||
|
|
||||||
<p>These donations primarily help us pay for our VPSes, domain names and other expenses related to crucial infrastructure we have to maintain. We also sometimes donate to developers who maintain software we rely heavily on such as our authentication provider.</p>
|
<p>
|
||||||
|
These donations primarily help us pay for our VPSes, domain names and other
|
||||||
|
expenses related to crucial infrastructure we have to maintain. We also
|
||||||
|
sometimes donate to developers who maintain software we rely heavily on such
|
||||||
|
as our authentication provider.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Donation methods</h2>
|
<h2>Donation methods</h2>
|
||||||
|
|
||||||
<p>You can currently donate by credit card through <a href="https://liberapay.com">Liberapay</a> and cryptocurrencies.</p>
|
<p>
|
||||||
|
You can currently donate by credit card through <a
|
||||||
|
href="https://liberapay.com">Liberapay</a
|
||||||
|
> and cryptocurrencies.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>Credit card</h3>
|
<h3>Credit card</h3>
|
||||||
<a href="https://liberapay.com/ProjectSegfault/donate" class="button !bg-amber !text-black w-fit"><div class="i-simple-icons:liberapay" /> Liberapay</a>
|
<a
|
||||||
|
href="https://liberapay.com/ProjectSegfault/donate"
|
||||||
|
class="button !bg-amber !text-black w-fit"
|
||||||
|
><div class="i-simple-icons:liberapay" />
|
||||||
|
Liberapay</a
|
||||||
|
>
|
||||||
|
|
||||||
<h3>Cryptocurrencies</h3>
|
<h3>Cryptocurrencies</h3>
|
||||||
<p>You can use <a href="https://projectsegfau.lt">projectsegfau.lt</a> as a crypto wallet address in supported OpenAlias clients such as <a href="https://mymonero.com">MyMonero</a>, <a href="https://electrum.org">Electrum</a> and <a href="https://electrum-ltc.org">Electrum-LTC</a>.</p>
|
<p>
|
||||||
|
You can use <a href="https://projectsegfau.lt">projectsegfau.lt</a> as a
|
||||||
|
crypto wallet address in supported OpenAlias clients such as
|
||||||
|
<a href="https://mymonero.com">MyMonero</a>,
|
||||||
|
<a href="https://electrum.org">Electrum</a>
|
||||||
|
and <a href="https://electrum-ltc.org">Electrum-LTC</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h4>Monero</h4>
|
<h4>Monero</h4>
|
||||||
|
|
||||||
<CryptoInfo address="47L7Qsto7XcifY3CdG18ySe5Tt83kpFLDLve9jQwbc9taPBLNGv6ZrJNUKpMG9Nj9zHgCZ4FQMSyt75e8Jvx12JFLtJyFdA" qr="Monero.png" />
|
<CryptoInfo
|
||||||
|
address="47L7Qsto7XcifY3CdG18ySe5Tt83kpFLDLve9jQwbc9taPBLNGv6ZrJNUKpMG9Nj9zHgCZ4FQMSyt75e8Jvx12JFLtJyFdA"
|
||||||
|
qr="Monero.png"
|
||||||
|
/>
|
||||||
|
|
||||||
<h4>Bitcoin</h4>
|
<h4>Bitcoin</h4>
|
||||||
|
|
||||||
<CryptoInfo address="bc1qrc8ywgp95a6p3zausp4nff70qzstp6h8z86sxd" qr="Bitcoin.png" />
|
<CryptoInfo
|
||||||
|
address="bc1qrc8ywgp95a6p3zausp4nff70qzstp6h8z86sxd"
|
||||||
|
qr="Bitcoin.png"
|
||||||
|
/>
|
||||||
|
|
||||||
<h4>Litecoin</h4>
|
<h4>Litecoin</h4>
|
||||||
|
|
||||||
<CryptoInfo address="ltc1qn3ald586h2ntt0n3zkvwsmju2e5vndgtvvgatj" qr="Litecoin.png" />
|
<CryptoInfo
|
||||||
|
address="ltc1qn3ald586h2ntt0n3zkvwsmju2e5vndgtvvgatj"
|
||||||
|
qr="Litecoin.png"
|
||||||
|
/>
|
||||||
|
@ -6,13 +6,19 @@
|
|||||||
{#if address}
|
{#if address}
|
||||||
<details class="p-0">
|
<details class="p-0">
|
||||||
<summary>Address</summary>
|
<summary>Address</summary>
|
||||||
<code class="break-words whitespace-normal">49burTxWHyqa9NkkC9PV33D79PrwARMq8aic4XezTx36i66qyLA3afYXicycTTA5st93CV5Rr9AGkKpeE5GPueRN2PkfFQN</code>
|
<code class="break-words whitespace-normal"
|
||||||
|
>49burTxWHyqa9NkkC9PV33D79PrwARMq8aic4XezTx36i66qyLA3afYXicycTTA5st93CV5Rr9AGkKpeE5GPueRN2PkfFQN</code
|
||||||
|
>
|
||||||
</details>
|
</details>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if qr}
|
{#if qr}
|
||||||
<details class="p-0">
|
<details class="p-0">
|
||||||
<summary>QR code</summary>
|
<summary>QR code</summary>
|
||||||
<img src="/qr/{qr}" alt="QR code" class="mt-2" />
|
<img
|
||||||
|
src="/qr/{qr}"
|
||||||
|
alt="QR code"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</details>
|
</details>
|
||||||
{/if}
|
{/if}
|
@ -4,7 +4,7 @@ import type { PageServerLoad } from "./$types";
|
|||||||
export const load = (() => {
|
export const load = (() => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Instances"
|
title: "Instances"
|
||||||
}
|
};
|
||||||
|
|
||||||
return { instances, ...meta };
|
return { instances, ...meta };
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
@ -6,7 +6,12 @@
|
|||||||
|
|
||||||
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4 !mb-0">
|
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4 !mb-0">
|
||||||
<span class="text-4xl font-bold">{data.title}</span>
|
<span class="text-4xl font-bold">{data.title}</span>
|
||||||
<a href="/instances/advanced" class="button sm:w-fit"><div class="i-ic:outline-computer" /> Advanced</a>
|
<a
|
||||||
|
href="/instances/advanced"
|
||||||
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-computer" />
|
||||||
|
Advanced</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@ -15,9 +20,16 @@
|
|||||||
<h2>{category.name}</h2>
|
<h2>{category.name}</h2>
|
||||||
<div class="flex flex-row flex-wrap gap-4">
|
<div class="flex flex-row flex-wrap gap-4">
|
||||||
{#each category.data as instance}
|
{#each category.data as instance}
|
||||||
<a href={instance.geo || instance.eu} class="flex flex-row items-center gap-4 rounded bg-secondary p-4 w-110 no-underline text-text">
|
<a
|
||||||
|
href={instance.geo || instance.eu}
|
||||||
|
class="flex flex-row items-center gap-4 rounded bg-secondary p-4 w-110 no-underline text-text"
|
||||||
|
>
|
||||||
{#if instance.icon}
|
{#if instance.icon}
|
||||||
<img src={instance.icon} alt="{instance.name} logo" class="h-20 rounded">
|
<img
|
||||||
|
src={instance.icon}
|
||||||
|
alt="{instance.name} logo"
|
||||||
|
class="h-20 rounded"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<span class="text-2xl">{instance.name}</span>
|
<span class="text-2xl">{instance.name}</span>
|
||||||
|
@ -4,7 +4,7 @@ import type { PageServerLoad } from "./$types";
|
|||||||
export const load = (() => {
|
export const load = (() => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Instances"
|
title: "Instances"
|
||||||
}
|
};
|
||||||
|
|
||||||
return { instances, ...meta };
|
return { instances, ...meta };
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
@ -6,7 +6,12 @@
|
|||||||
|
|
||||||
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4 !mb-0">
|
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4 !mb-0">
|
||||||
<span class="text-4xl font-bold">{data.title}</span>
|
<span class="text-4xl font-bold">{data.title}</span>
|
||||||
<a href="/instances" class="button sm:w-fit"><div class="i-ic:outline-computer" /> Simple</a>
|
<a
|
||||||
|
href="/instances"
|
||||||
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-computer" />
|
||||||
|
Simple</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -12,18 +12,35 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="/legal/privacy-policy">Privacy policy</a></li>
|
<li><a href="/legal/privacy-policy">Privacy policy</a></li>
|
||||||
<li><a href="/legal/tos">Terms of service</a></li>
|
<li><a href="/legal/tos">Terms of service</a></li>
|
||||||
<li><a href="https://git.projectsegfau.lt/ProjectSegfault/transparency/">Transparency reports</a></li>
|
<li>
|
||||||
|
<a href="https://git.projectsegfau.lt/ProjectSegfault/transparency/"
|
||||||
|
>Transparency reports</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Legal FAQ</h2>
|
<h2>Legal FAQ</h2>
|
||||||
|
|
||||||
<h3>What do I do if a user is disturbing me?</h3>
|
<h3>What do I do if a user is disturbing me?</h3>
|
||||||
|
|
||||||
<p>If some user of our services is disturbing you, you should immediately contact us. You can do that either by <a href="mailto:contact@projectsegfau.lt">emailing us</a> or sending us a message on <a href="https://matrix.to/#/#support:projectsegfau.lt">the Matrix support channel</a>. If you prefer to keep your report private on Matrix, contact one of our team members. You can find their info on <a href="/team">our team page</a>.</p>
|
<p>
|
||||||
|
If some user of our services is disturbing you, you should immediately
|
||||||
|
contact us. You can do that either by <a
|
||||||
|
href="mailto:contact@projectsegfau.lt">emailing us</a
|
||||||
|
>
|
||||||
|
or sending us a message on
|
||||||
|
<a href="https://matrix.to/#/#support:projectsegfau.lt"
|
||||||
|
>the Matrix support channel</a
|
||||||
|
>. If you prefer to keep your report private on Matrix, contact one of our
|
||||||
|
team members. You can find their info on <a href="/team">our team page</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>How can I trust you?</h3>
|
<h3>How can I trust you?</h3>
|
||||||
|
|
||||||
<p>We have written a privacy policy, which is accessible through this website! You can find it <a href="/legal/privacy-policy">here</a>.</p>
|
<p>
|
||||||
|
We have written a privacy policy, which is accessible through this website!
|
||||||
|
You can find it <a href="/legal/privacy-policy">here</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
li::before {
|
li::before {
|
||||||
|
@ -3,6 +3,7 @@ import type { PageLoad } from "./$types";
|
|||||||
export const load = (() => {
|
export const load = (() => {
|
||||||
return {
|
return {
|
||||||
title: "Boring legal stuff",
|
title: "Boring legal stuff",
|
||||||
description: "These are some documents concerning transparency, privacy and safety."
|
description:
|
||||||
|
"These are some documents concerning transparency, privacy and safety."
|
||||||
};
|
};
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
@ -9,36 +9,103 @@
|
|||||||
|
|
||||||
<h2>We don't collect more information than we need to.</h2>
|
<h2>We don't collect more information than we need to.</h2>
|
||||||
|
|
||||||
<PMargin>We have disabled request logging. This is because it is extremely identifiable. This means that, for example, what website you visited and what path you visited (like https://libreddit.projectsegfau.lt/r/cats), your IP address, your User-Agent will not be logged by us!</PMargin>
|
<PMargin
|
||||||
|
>We have disabled request logging. This is because it is extremely
|
||||||
|
identifiable. This means that, for example, what website you visited and
|
||||||
|
what path you visited (like https://libreddit.projectsegfau.lt/r/cats), your
|
||||||
|
IP address, your User-Agent will not be logged by us!</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>Some of our services have things like databases and things, for example <a href="https://chat.projectsegfau.lt">Matrix</a>, but that should be pretty obvious the moment you make an account on there. They may collect some things, however we barely even access those databases so you should be fine.</PMargin>
|
<PMargin
|
||||||
|
>Some of our services have things like databases and things, for example <a
|
||||||
|
href="https://chat.projectsegfau.lt">Matrix</a
|
||||||
|
>, but that should be pretty obvious the moment you make an account on
|
||||||
|
there. They may collect some things, however we barely even access those
|
||||||
|
databases so you should be fine.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin><a href="https://chat.projectsegfau.lt">Matrix</a> logs IPs, there is nothing we can do to prevent that. We will only check IP addresses whenever there is serious abuse coming from someone's Matrix account, so we can ban their IP from using Project Segfault services. This includes, but is not limited to ban evading and harassment of communities and people.</PMargin>
|
<PMargin
|
||||||
|
><a href="https://chat.projectsegfau.lt">Matrix</a> logs IPs, there is nothing
|
||||||
|
we can do to prevent that. We will only check IP addresses whenever there is
|
||||||
|
serious abuse coming from someone's Matrix account, so we can ban their IP from
|
||||||
|
using Project Segfault services. This includes, but is not limited to ban evading
|
||||||
|
and harassment of communities and people.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>If you want the data we've collected on you to be sent, please contact us on Matrix in <a href="https://matrix.to/#/#gdpr:projectsegfau.lt">this room</a>, or e-mail us at <a href="mailto:contact@projectsegfau.lt">contact@projectsegfau.lt</a>. You don't need an account on our Matrix instance in order to chat there, you can choose from a bunch of public instances or make your own.</PMargin>
|
<PMargin
|
||||||
|
>If you want the data we've collected on you to be sent, please contact us
|
||||||
|
on Matrix in <a href="https://matrix.to/#/#gdpr:projectsegfau.lt"
|
||||||
|
>this room</a
|
||||||
|
>, or e-mail us at
|
||||||
|
<a href="mailto:contact@projectsegfau.lt">contact@projectsegfau.lt</a>. You
|
||||||
|
don't need an account on our Matrix instance in order to chat there, you can
|
||||||
|
choose from a bunch of public instances or make your own.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<h2>For our website and blog</h2>
|
<h2>For our website and blog</h2>
|
||||||
|
|
||||||
<PMargin>We have Plausible analytics installed on our website (the one you are on right now!), and our <a href="https://blog.projectsegfau.lt">blog</a>, which means we can just easily see what part of our site you're on, what country you are from, what platform you are from, potentially a referrer and all those get clumped up together.</PMargin>
|
<PMargin
|
||||||
|
>We have Plausible analytics installed on our website (the one you are on
|
||||||
|
right now!), and our <a href="https://blog.projectsegfau.lt">blog</a>, which
|
||||||
|
means we can just easily see what part of our site you're on, what country
|
||||||
|
you are from, what platform you are from, potentially a referrer and all
|
||||||
|
those get clumped up together.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin class="italic">Plausible Analytics is completely anonymous. We can't exactly figure out who you are through Plausible. It is also GDPR compliant.</PMargin>
|
<PMargin class="italic"
|
||||||
|
>Plausible Analytics is completely anonymous. We can't exactly figure out
|
||||||
|
who you are through Plausible. It is also GDPR compliant.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>We use analytics in order to see how many visitors we get every day, what pages get visited the most, what countries visit our website the most etc. uBlock and other privacy extensions block Plausible by default.</PMargin>
|
<PMargin
|
||||||
|
>We use analytics in order to see how many visitors we get every day, what
|
||||||
|
pages get visited the most, what countries visit our website the most etc.
|
||||||
|
uBlock and other privacy extensions block Plausible by default.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>If you would like to see our website's statistics, <a href="https://analytics.projectsegfau.lt/projectsegfau.lt">look no further</a>.</PMargin>
|
<PMargin
|
||||||
|
>If you would like to see our website's statistics, <a
|
||||||
|
href="https://analytics.projectsegfau.lt/projectsegfau.lt"
|
||||||
|
>look no further</a
|
||||||
|
>.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<h2>We don't give any of the data we collect to anyone outside of Project Segfault.</h2>
|
<h2>
|
||||||
|
We don't give any of the data we collect to anyone outside of Project
|
||||||
|
Segfault.
|
||||||
|
</h2>
|
||||||
|
|
||||||
<PMargin>What we just described above won't be sold or given to anyone outside of Project Segfault, except for anonymous analytics like the website. Some data could be given to law enforcement if they have a warrant.</PMargin>
|
<PMargin
|
||||||
|
>What we just described above won't be sold or given to anyone outside of
|
||||||
|
Project Segfault, except for anonymous analytics like the website. Some data
|
||||||
|
could be given to law enforcement if they have a warrant.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>There is no reason for anyone outside of Project Segfault to see your data. In fact, Project Segfault really has no reason to see your data, unless there's something to suspect.</PMargin>
|
<PMargin
|
||||||
|
>There is no reason for anyone outside of Project Segfault to see your data.
|
||||||
|
In fact, Project Segfault really has no reason to see your data, unless
|
||||||
|
there's something to suspect.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<h2>Data may be cleared at any point upon request, but...</h2>
|
<h2>Data may be cleared at any point upon request, but...</h2>
|
||||||
|
|
||||||
<PMargin>There are times when we are unable to delete "your" data because we sometimes we do not have a concept of "you". If you don't create an account on the services you use, we have no way to know what data is tied to "you" as most logs are anonymized.</PMargin>
|
<PMargin
|
||||||
|
>There are times when we are unable to delete "your" data because we
|
||||||
|
sometimes we do not have a concept of "you". If you don't create an account
|
||||||
|
on the services you use, we have no way to know what data is tied to "you"
|
||||||
|
as most logs are anonymized.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>Often times you can clear your own data simply by deleting your account.</PMargin>
|
<PMargin
|
||||||
|
>Often times you can clear your own data simply by deleting your account.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin>As we said, you can request any GDPR/Privacy things in <a href="https://matrix.to/#/#gdpr:projectsegfau.lt">this Matrix room</a>. But, if there's any ongoing investigations and a law enforcement agency contacts us, we will comply. But, we will only do it if we can verify it's official and they have a warrant.</PMargin>
|
<PMargin
|
||||||
|
>As we said, you can request any GDPR/Privacy things in <a
|
||||||
|
href="https://matrix.to/#/#gdpr:projectsegfau.lt">this Matrix room</a
|
||||||
|
>. But, if there's any ongoing investigations and a law enforcement agency
|
||||||
|
contacts us, we will comply. But, we will only do it if we can verify it's
|
||||||
|
official and they have a warrant.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<span class="italic">Last updated 09.08.2022 16:57 UTC+1</span>
|
<span class="italic">Last updated 09.08.2022 16:57 UTC+1</span>
|
@ -4,17 +4,41 @@
|
|||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
|
|
||||||
<ol class="list-decimal ml-7">
|
<ol class="list-decimal ml-7">
|
||||||
<li>Do not use our services to (D)DOS or attempt to disrupt someone else’s online stability.</li>
|
<li>
|
||||||
|
Do not use our services to (D)DOS or attempt to disrupt someone else’s
|
||||||
|
online stability.
|
||||||
|
</li>
|
||||||
<li>Do not use our services to dox someone.</li>
|
<li>Do not use our services to dox someone.</li>
|
||||||
<li>Do not do anything on our services that would be illegal in France, the USA, Luxembourg, and India.</li>
|
<li>
|
||||||
|
Do not do anything on our services that would be illegal in France, the
|
||||||
|
USA, Luxembourg, and India.
|
||||||
|
</li>
|
||||||
<li>Do not harass people using our services.</li>
|
<li>Do not harass people using our services.</li>
|
||||||
<li>While we do try to keep your data safe, you have to acknowledge that we are not responsible if anything unintentional happens (such as data loss, inability to extract your data due to the server being down or something else.). It is also your responsibility to keep a backup of your data if it matters to you.</li>
|
<li>
|
||||||
<li>The services provided by Project Segfault are provided as is. We do not warrant the reliability, accessibility or quality of our services and we are not responsible for ANY DAMAGES WHATSOEVER by using our services.</li>
|
While we do try to keep your data safe, you have to acknowledge that we
|
||||||
|
are not responsible if anything unintentional happens (such as data
|
||||||
|
loss, inability to extract your data due to the server being down or
|
||||||
|
something else.). It is also your responsibility to keep a backup of
|
||||||
|
your data if it matters to you.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The services provided by Project Segfault are provided as is. We do not
|
||||||
|
warrant the reliability, accessibility or quality of our services and we
|
||||||
|
are not responsible for ANY DAMAGES WHATSOEVER by using our services.
|
||||||
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<PMargin>If you fail to comply with these terms we will terminate your access to our services and if you've done something illegal, report you to the police.</PMargin>
|
<PMargin
|
||||||
|
>If you fail to comply with these terms we will terminate your access to our
|
||||||
|
services and if you've done something illegal, report you to the police.</PMargin
|
||||||
|
>
|
||||||
|
|
||||||
<PMargin class="italic">We will only report people to the police if they are using our services to do something that is morally unacceptable. We will do so without issuing any warnings.</PMargin>
|
<PMargin class="italic"
|
||||||
|
>We will only report people to the police if they are using our services to
|
||||||
|
do something that is morally unacceptable. We will do so without issuing any
|
||||||
|
warnings.</PMargin
|
||||||
|
>
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { env } from "$env/dynamic/private";
|
import { env } from "$env/dynamic/private";
|
||||||
import type { PageServerLoad } from "./$types"
|
import type { PageServerLoad } from "./$types";
|
||||||
|
|
||||||
export const load = (async ({ locals }) => {
|
export const load = (async ({ locals }) => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Login"
|
title: "Login"
|
||||||
}
|
};
|
||||||
|
|
||||||
const hasAuth = !env.AUTH_CLIENT_ID || !env.AUTH_CLIENT_SECRET || !env.AUTH_ISSUER || !env.AUTH_TRUST_HOST || !env.AUTH_SECRET ? false : true;
|
const hasAuth =
|
||||||
|
!env.AUTH_CLIENT_ID ||
|
||||||
|
!env.AUTH_CLIENT_SECRET ||
|
||||||
|
!env.AUTH_ISSUER ||
|
||||||
|
!env.AUTH_TRUST_HOST ||
|
||||||
|
!env.AUTH_SECRET
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session: hasAuth ? await locals.getSession() : undefined,
|
session: hasAuth ? await locals.getSession() : undefined,
|
||||||
hasAuth,
|
hasAuth,
|
||||||
...meta
|
...meta
|
||||||
}
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { signIn, signOut } from '@auth/sveltekit/client';
|
import { signIn, signOut } from "@auth/sveltekit/client";
|
||||||
import { page } from '$app/stores';
|
import { page } from "$app/stores";
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from "./$types";
|
||||||
const buttonStyles = "button w-fit";
|
const buttonStyles = "button w-fit";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
@ -14,20 +14,34 @@
|
|||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-row items-center gap-1">
|
<div class="flex flex-row items-center gap-1">
|
||||||
<span>Signed in as</span><br />
|
<span>Signed in as</span><br />
|
||||||
<span class="font-extrabold">{$page?.data?.session?.user?.email}</span>
|
<span class="font-extrabold"
|
||||||
|
>{$page?.data?.session?.user?.email}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<a href="/admin">Go to admin dashboard</a>
|
<a href="/admin">Go to admin dashboard</a>
|
||||||
<button on:click={() => signOut()} class={buttonStyles}><div class="i-ic:outline-logout" /> Sign out</button>
|
<button
|
||||||
|
on:click={() => signOut()}
|
||||||
|
class={buttonStyles}
|
||||||
|
><div class="i-ic:outline-logout" />
|
||||||
|
Sign out</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<span>You are not signed in</span>
|
<span>You are not signed in</span>
|
||||||
<button on:click={() => signIn("authentik")} class={buttonStyles}><div class="i-ic:outline-login" /> Sign in using Authentik</button>
|
<button
|
||||||
|
on:click={() => signIn("authentik")}
|
||||||
|
class={buttonStyles}
|
||||||
|
><div class="i-ic:outline-login" />
|
||||||
|
Sign in using Authentik</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<span>Authentik is not configured</span>
|
<span>Authentik is not configured</span>
|
||||||
<a href="https://goauthentik.io/docs/installation">Configure Authentik</a>
|
<a href="https://goauthentik.io/docs/installation"
|
||||||
|
>Configure Authentik</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
@ -3,7 +3,7 @@ import type { PageServerLoad } from "./$types";
|
|||||||
export const load = (async ({ fetch }) => {
|
export const load = (async ({ fetch }) => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Pubnix"
|
title: "Pubnix"
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = await fetch("https://publapi.p.projectsegfau.lt/users");
|
const request = await fetch("https://publapi.p.projectsegfau.lt/users");
|
||||||
@ -14,7 +14,11 @@ export const load = (async ({ fetch }) => {
|
|||||||
...meta
|
...meta
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { error: true, message: "Error: " + request.status, ...meta };
|
return {
|
||||||
|
error: true,
|
||||||
|
message: "Error: " + request.status,
|
||||||
|
...meta
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { error: true, message: "Error: " + err, ...meta };
|
return { error: true, message: "Error: " + err, ...meta };
|
||||||
|
@ -4,14 +4,29 @@
|
|||||||
|
|
||||||
import User, { type UserType } from "./User.svelte";
|
import User, { type UserType } from "./User.svelte";
|
||||||
|
|
||||||
const isOnline = (user: UserType) => user.online
|
const isOnline = (user: UserType) => user.online;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4">
|
<div class="h1-no-lg flex flex-col sm:(flex-row items-center) gap-4">
|
||||||
<span class="text-4xl font-bold">Pubnix</span>
|
<span class="text-4xl font-bold">Pubnix</span>
|
||||||
<a href="/pubnix/register" class="button sm:w-fit"><div class="i-ic:outline-plus" /> Register</a>
|
<a
|
||||||
<a href="/pubnix/users" class="button sm:w-fit"><div class="i-ic:outline-people" /> Users</a>
|
href="/pubnix/register"
|
||||||
<a href="/pubnix/faq" class="button sm:w-fit"><div class="i-ic:outline-question-mark" /> FAQ</a>
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-plus" />
|
||||||
|
Register</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/pubnix/users"
|
||||||
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-people" />
|
||||||
|
Users</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/pubnix/faq"
|
||||||
|
class="button sm:w-fit"
|
||||||
|
><div class="i-ic:outline-question-mark" />
|
||||||
|
FAQ</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Online users</h2>
|
<h2>Online users</h2>
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<script lang="ts">
|
<script
|
||||||
export let user: UserType;
|
context="module"
|
||||||
import dayjs from "dayjs";
|
lang="ts"
|
||||||
</script>
|
>
|
||||||
|
|
||||||
<script context="module" lang="ts">
|
|
||||||
export type UserType = {
|
export type UserType = {
|
||||||
name: string;
|
name: string;
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
@ -20,7 +18,14 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 rounded bg-secondary p-4 w-110 no-underline text-text">
|
<script lang="ts">
|
||||||
|
export let user: UserType;
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-4 rounded bg-secondary p-4 w-110 no-underline text-text"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-2 flex-1">
|
<div class="flex flex-col gap-2 flex-1">
|
||||||
<div>
|
<div>
|
||||||
{#if user.fullName}
|
{#if user.fullName}
|
||||||
@ -38,7 +43,9 @@
|
|||||||
{#if user.loc}
|
{#if user.loc}
|
||||||
<span class="button w-fit !bg-alt !text-text">{user.loc}</span>
|
<span class="button w-fit !bg-alt !text-text">{user.loc}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="button w-fit !bg-alt !text-text">Joined: {dayjs.unix(user.created).format("DD/MM/YYYY")}</span>
|
<span class="button w-fit !bg-alt !text-text"
|
||||||
|
>Joined: {dayjs.unix(user.created).format("DD/MM/YYYY")}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="children:text-text flex flex-row items-center gap-4 text-lg">
|
<div class="children:text-text flex flex-row items-center gap-4 text-lg">
|
||||||
{#if user.email}
|
{#if user.email}
|
||||||
@ -58,7 +65,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if user.capsule}
|
{#if user.capsule}
|
||||||
<a href={user.capsule} class="no-underline text-base">Gemini</a>
|
<a
|
||||||
|
href={user.capsule}
|
||||||
|
class="no-underline text-base">Gemini</a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -7,4 +7,7 @@
|
|||||||
<h1>{data.title}</h1>
|
<h1>{data.title}</h1>
|
||||||
|
|
||||||
<h2>What is a pubnix?</h2>
|
<h2>What is a pubnix?</h2>
|
||||||
<span>A pubnix is a [[Unix?]] server provided by a person or a group to a group for non-commercial recreational goals.</span>
|
<span
|
||||||
|
>A pubnix is a [[Unix?]] server provided by a person or a group to a group
|
||||||
|
for non-commercial recreational goals.</span
|
||||||
|
>
|
||||||
|
@ -13,36 +13,65 @@ export const actions: Actions = {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
const BodyTypeSchema = Joi.object({
|
const BodyTypeSchema = Joi.object({
|
||||||
username: Joi.string().required().alphanum().message("Username must be alphanumeric"),
|
username: Joi.string()
|
||||||
|
.required()
|
||||||
|
.alphanum()
|
||||||
|
.message("Username must be alphanumeric"),
|
||||||
email: Joi.string().email().required(),
|
email: Joi.string().email().required(),
|
||||||
ssh: Joi.string().required().pattern(/^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) [A-Za-z0-9+/]+[=]{0,3}( [^@]+@[^@]+)?$/).message("Invalid SSH key"),
|
ssh: Joi.string()
|
||||||
|
.required()
|
||||||
|
.pattern(
|
||||||
|
/^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) [A-Za-z0-9+/]+[=]{0,3}( [^@]+@[^@]+)?$/
|
||||||
|
)
|
||||||
|
.message("Invalid SSH key"),
|
||||||
ip: Joi.string().required().ip()
|
ip: Joi.string().required().ip()
|
||||||
});
|
});
|
||||||
|
|
||||||
formData.append("ip", getClientAddress());
|
formData.append("ip", getClientAddress());
|
||||||
|
|
||||||
if (BodyTypeSchema.validate(Object.fromEntries(formData.entries())).error) {
|
if (
|
||||||
return fail(400, { error: true, message: String(BodyTypeSchema.validate(Object.fromEntries(formData.entries())).error) });
|
BodyTypeSchema.validate(Object.fromEntries(formData.entries()))
|
||||||
|
.error
|
||||||
|
) {
|
||||||
|
return fail(400, {
|
||||||
|
error: true,
|
||||||
|
message: String(
|
||||||
|
BodyTypeSchema.validate(
|
||||||
|
Object.fromEntries(formData.entries())
|
||||||
|
).error
|
||||||
|
)
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const request = await fetch("https://publapi.p.projectsegfau.lt/signup", {
|
const request = await fetch(
|
||||||
|
"https://publapi.p.projectsegfau.lt/signup",
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
},
|
},
|
||||||
body: new URLSearchParams(formData as any).toString()
|
body: new URLSearchParams(formData as any).toString()
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const json = await request.json();
|
const json = await request.json();
|
||||||
|
|
||||||
if (request.ok) {
|
if (request.ok) {
|
||||||
return { success: true, message: json.message, username: json.username, email: json.email };
|
return {
|
||||||
|
success: true,
|
||||||
|
message: json.message,
|
||||||
|
username: json.username,
|
||||||
|
email: json.email
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return fail(400, { error: true, message: "Error: " + request.status });
|
return fail(400, {
|
||||||
|
error: true,
|
||||||
|
message: "Error: " + request.status
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { error: true, message: "Error: " + err };
|
return { error: true, message: "Error: " + err };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -9,10 +9,21 @@
|
|||||||
method="POST"
|
method="POST"
|
||||||
class="flex flex-col gap-4 w-fit children:(!bg-secondary text-start text-text rounded p-2)"
|
class="flex flex-col gap-4 w-fit children:(!bg-secondary text-start text-text rounded p-2)"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
<input type="text" name="username" placeholder="Username" />
|
type="text"
|
||||||
<input type="email" name="email" placeholder="Email" />
|
name="username"
|
||||||
<textarea name="ssh" placeholder="SSH public key" class="resize w-60 h-30 sm:(w-100 h-50)" />
|
placeholder="Username"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Email"
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
name="ssh"
|
||||||
|
placeholder="SSH public key"
|
||||||
|
class="resize w-60 h-30 sm:(w-100 h-50)"
|
||||||
|
/>
|
||||||
|
|
||||||
{#if form?.success}
|
{#if form?.success}
|
||||||
{form.message}
|
{form.message}
|
||||||
@ -21,5 +32,9 @@
|
|||||||
{#if form?.error}
|
{#if form?.error}
|
||||||
{form.message}
|
{form.message}
|
||||||
{/if}
|
{/if}
|
||||||
<button type="submit" class="transition-all duration-200 text-text hover:brightness-70">Submit</button>
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="transition-all duration-200 text-text hover:brightness-70"
|
||||||
|
>Submit</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
@ -3,7 +3,7 @@ import type { PageServerLoad } from "./$types";
|
|||||||
export const load = (async ({ fetch }) => {
|
export const load = (async ({ fetch }) => {
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Pubnix users"
|
title: "Pubnix users"
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = await fetch("https://publapi.p.projectsegfau.lt/users");
|
const request = await fetch("https://publapi.p.projectsegfau.lt/users");
|
||||||
@ -14,7 +14,11 @@ export const load = (async ({ fetch }) => {
|
|||||||
...meta
|
...meta
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { error: true, message: "Error: " + request.status, ...meta };
|
return {
|
||||||
|
error: true,
|
||||||
|
message: "Error: " + request.status,
|
||||||
|
...meta
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { error: true, message: "Error: " + err, ...meta };
|
return { error: true, message: "Error: " + err, ...meta };
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
<div class="flex flex-row flex-wrap gap-4">
|
<div class="flex flex-row flex-wrap gap-4">
|
||||||
{#each Team as member}
|
{#each Team as member}
|
||||||
<div class="flex flex-col gap-4 rounded bg-secondary p-4 w-110 no-underline text-text">
|
<div
|
||||||
|
class="flex flex-col gap-4 rounded bg-secondary p-4 w-110 no-underline text-text"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-2 flex-1">
|
<div class="flex flex-col gap-2 flex-1">
|
||||||
<span class="text-2xl">{member.name} - {member.position}</span>
|
<span class="text-2xl">{member.name} - {member.position}</span>
|
||||||
{#if member.description}
|
{#if member.description}
|
||||||
@ -18,11 +20,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="children:text-text flex flex-row gap-4 text-lg">
|
<div class="children:text-text flex flex-row gap-4 text-lg">
|
||||||
{#if member.website}
|
{#if member.website}
|
||||||
<a href={member.website}><div class="i-ic:outline-language" /></a>
|
<a href={member.website}
|
||||||
|
><div class="i-ic:outline-language" /></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if member.matrix}
|
{#if member.matrix}
|
||||||
<a href={member.matrix}><div class="i-simple-icons:matrix" /></a>
|
<a href={member.matrix}
|
||||||
|
><div class="i-simple-icons:matrix" /></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if member.git}
|
{#if member.git}
|
||||||
@ -30,11 +36,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if member.email}
|
{#if member.email}
|
||||||
<a href="mailto:{member.email}"><div class="i-ic:outline-email" /></a>
|
<a href="mailto:{member.email}"
|
||||||
|
><div class="i-ic:outline-email" /></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if member.pgp}
|
{#if member.pgp}
|
||||||
<a href={member.pgp}><div class="i-ic:outline-vpn-key" /></a>
|
<a href={member.pgp}><div class="i-ic:outline-vpn-key" /></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import adapter from '@sveltejs/adapter-node';
|
import adapter from "@sveltejs/adapter-node";
|
||||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
import { vitePreprocess } from "@sveltejs/kit/vite";
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import unoCSS from "unocss/vite";;
|
import unoCSS from "unocss/vite";
|
||||||
import type { UserConfig } from 'vite';
|
import type { UserConfig } from "vite";
|
||||||
|
|
||||||
const config: UserConfig = {
|
const config: UserConfig = {
|
||||||
plugins: [sveltekit(), unoCSS()]
|
plugins: [sveltekit(), unoCSS()]
|
||||||
|
Loading…
Reference in New Issue
Block a user