This commit is contained in:
Akis 2023-01-25 19:11:11 +02:00
parent add264a5d6
commit 07c0cc4a69
Signed by untrusted user: akis
GPG Key ID: 267BF5C6677944ED
117 changed files with 1554 additions and 3593 deletions

View File

@ -1,25 +1,36 @@
name: Docker
name: Docker dev
on:
push:
branches:
- "dev"
push:
branches:
- 'dev'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
tags: realprojectsegfault/website:dev
build:
name: 'Build'
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: "Build:checkout"
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ProjectSegfault
password: ${{ secrets.ACCESS_TOKEN }}
- name: 'Build:dockerimage'
uses: docker/build-push-action@v3
with:
tags: ghcr.io/ProjectSegfault/website:dev
context: "."
push: true
no-cache: true

View File

@ -1,25 +1,36 @@
name: Docker
on:
push:
branches:
- "master"
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
tags: realprojectsegfault/website:latest
build:
name: 'Build'
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: "Build:checkout"
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: akisblack
password: ${{ secrets.ACCESS_TOKEN }}
- name: 'Build:dockerimage'
uses: docker/build-push-action@v3
with:
tags: ghcr.io/akisblack/website:latest
context: "."
push: true
no-cache: true

3
.gitignore vendored
View File

@ -6,4 +6,5 @@ node_modules
.env
.env.*
!.env.example
package-lock.json
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

View File

@ -3,5 +3,8 @@
"useTabs": true,
"bracketSpacing": true,
"singleAttributePerLine": true,
"trailingComma": "none"
"trailingComma": "none",
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -4,13 +4,12 @@ Live at [projectsegfau.lt](https://projectsegfau.lt).
## Developing
> You need a lot of infrastructure to run a complete version of the website including: Ghost CMS deployment, Authentik authentication, a Discord channel with a webhook and a hCaptcha sitekey and secret from a hCaptcha account.
> You need a lot of infrastructure to run a complete version of the website including: Ghost CMS deployment and Authentik authentication.
### Prerequisites
- Install [node.js](https://nodejs.org).
- Install [pnpm](https://pnpm.io/).
- Install [MongoDB](https://mongodb.com).
- Learn [Svelte](https://svelte.dev).
- Add the environment variables from the [environment variables section](#environment-variables).
@ -40,14 +39,11 @@ The website has the following **mandatory** environment variables
| Name | Description |
|:------------------ |:------------------------- |
| AUTH_SECRET | Random 32 char secret |
| AUTH_CLIENT_ID | Authentik client ID |
| AUTH_CLIENT_SECRET | Authentik client secret |
| AUTH_ISSUER | Authentication issuer URL |
| AUTH_TRUST_HOST | Your domain |
| HCAPTCHA_SECRET | Your hCaptcha secret |
| HCAPTCHA_SITEKEY | Your hCaptcha sitekey |
| WEBHOOK | Your Discord webhook URL |
| AUTH_SECRET | Random 32 char secret |
| GHOST_URL | Your Ghost CMS URL |
| GHOST_API_KEY | Your Ghost CMS API key |
| DB_URL | Your MongoDB url |
| ORIGIN | Your domain |

View File

@ -1,38 +1,11 @@
services:
website:
container_name: website
image: realprojectsegfault/website:latest # or :dev if you want to use the dev version
image: ghcr.io/ProjectSegfault/website:latest
restart: unless-stopped
# uncomment these lines if you want to build from source
#build:
# context: .
# dockerfile: Dockerfile
ports:
- 1339:3000 # change the first number to whatever port you want to use
environment: # these are documented in the readme
AUTH_SECRET: ${AUTH_SECRET}
AUTH_CLIENT_ID: ${AUTH_CLIENT_ID}
AUTH_CLIENT_SECRET: ${AUTH_CLIENT_SECRET}
AUTH_ISSUER: ${AUTH_ISSUER}
AUTH_TRUST_HOST: ${AUTH_TRUST_HOST}
HCAPTCHA_SECRET: ${HCAPTCHA_SECRET}
HCAPTCHA_SITEKEY: ${HCAPTCHA_SITEKEY}
WEBHOOK: ${WEBHOOK}
GHOST_API_KEY: ${GHOST_API_KEY}
DB_URL: ${DB_URL}
ORIGIN: ${ORIGIN}
website-db: # this is the mongodb database container
image: mongo:6
container_name: website-db
restart: unless-stopped
volumes:
- website-db-data:/data/db
environment: # these are documented in the readme
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
MONGO_INITDB_DATABASE: website
command: [--auth]
volumes:
website-db-data:
- "127.0.0.1:1339:3000"

View File

@ -1,47 +1,40 @@
{
"name": "project-segfault-website",
"version": "2.0.0",
"version": "3.0.0",
"private": true,
"scripts": {
"dev": "vite",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier -w --plugin-search-dir=. ."
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@iconify-json/ic": "^1.1.12",
"@iconify-json/simple-icons": "^1.1.40",
"@sveltejs/adapter-node": "1.0.0",
"@sveltejs/kit": "1.0.1",
"@types/marked": "^4.0.8",
"@iconify-json/simple-icons": "^1.1.41",
"@sveltejs/adapter-node": "^1.1.4",
"@sveltejs/kit": "^1.1.4",
"@types/sanitize-html": "^2.8.0",
"axios": "^1.2.2",
"consola": "^2.15.3",
"@unocss/reset": "^0.48.4",
"axios": "^1.2.4",
"dayjs": "^1.11.7",
"discord-webhook-node": "^1.1.8",
"marked": "^4.2.5",
"mdsvex": "^0.10.6",
"prettier": "^2.8.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"sanitize-html": "^2.8.1",
"svelte": "^3.55.0",
"svelte-check": "^3.0.1",
"svelte": "^3.55.1",
"svelte-check": "^3.0.2",
"svelte-dark-mode": "^2.1.0",
"svelte-hcaptcha": "^0.1.1",
"svelte-seo": "^1.4.1",
"svelte-vertical-timeline": "^0.0.2",
"tslib": "^2.4.1",
"typescript": "^4.9.4",
"unocss": "^0.48.0",
"vite": "4.0.3"
"unocss": "^0.48.4",
"vite": "^4.0.4"
},
"type": "module",
"dependencies": {
"@auth/core": "^0.2.4",
"@auth/sveltekit": "^0.1.11",
"dotenv": "^16.0.3",
"joi": "^17.7.0",
"mongodb": "^4.13.0"
"@auth/core": "^0.2.5",
"@auth/sveltekit": "^0.1.12",
"joi": "^17.7.0"
}
}

1673
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

77
src/app.css Normal file
View File

@ -0,0 +1,77 @@
@font-face {
font-family: "JetBrains Mono";
src: url("/JetBrainsMono.woff2");
font-display: swap;
}
html, html.light {
--accent: #00a584;
--accent-translucent: #00a58498;
--font-primary: "JetBrains Mono", monospace;
--primary: #ffffff;
--secondary: #e9e9e9;
--tertiary: #939393;
--text: #444444;
--grey: #cecece;
--alt: #ddd;
--alt-text: #333;
--black: #151515;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
html {
--primary: #151515;
--secondary: #1d1d1d;
--tertiary: #353535;
--text: #ffffffde;
--grey: #5454547a;
--alt: #333;
--alt-text: #ddd;
color-scheme: dark;
}
}
body {
@apply bg-primary text-text font-primary m-0 leading-loose min-h-screen transition-colors duration-200;
}
::selection {
@apply bg-accentTranslucent;
}
a {
@apply text-accent underline underline-offset-4 transition-filter duration-200;
}
a:hover {
@apply brightness-70;
}
h1 {
@apply text-4xl font-bold my-8 border-b-2 border-accent pb-2;
}
.h1-no-lg {
@apply my-8 border-b-2 border-accent pb-2;
}
h2 {
@apply text-3xl font-bold my-8;
}
h3 {
@apply text-2xl font-bold my-8;
}
h4 {
@apply text-xl font-bold my-8;
}
details {
@apply cursor-pointer;
}
.button {
@apply px-2 py-1 bg-accent text-primary rounded no-underline flex flex-row items-center gap-2;
}

17
src/app.d.ts vendored
View File

@ -1,10 +1,13 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {}
// and what to do when importing types
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

View File

@ -2,14 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link
rel="icon"
href="%sveltekit.assets%/logo_transparent.svg"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

View File

@ -1,27 +1,44 @@
import { SvelteKitAuth } from "@auth/sveltekit"
import Authentik from '@auth/core/providers/authentik';
import { SvelteKitAuth } from "@auth/sveltekit";
import Authentik from "@auth/core/providers/authentik";
import { env } from "$env/dynamic/private";
import statusData from "$lib/statusData";
import map from "$lib/map";
import type { Provider } from "@auth/core/providers";
import type { Profile } from "@auth/core/types";
import { redirect, type Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
export const handle = SvelteKitAuth({
providers: [
//@ts-ignore
Authentik({
clientId: env.AUTH_CLIENT_ID,
clientSecret: env.AUTH_CLIENT_SECRET,
issuer: env.AUTH_ISSUER
})
]
})
const hasAuth = !env.AUTH_CLIENT_ID || !env.AUTH_CLIENT_SECRET || !env.AUTH_ISSUER || !env.AUTH_TRUST_HOST || !env.AUTH_SECRET ? false : true;
const updateMap = () => {
map.set("data", {
status: statusData,
updated: Math.floor(Date.now() / 1000)
});
};
updateMap();
setInterval(updateMap, 30000);
export const handle: Handle = sequence(
//@ts-ignore
SvelteKitAuth({
providers: [
Authentik({
clientId: env.AUTH_CLIENT_ID,
clientSecret: env.AUTH_CLIENT_SECRET,
issuer: env.AUTH_ISSUER
}) as Provider<Profile>
]
}),
hasAuth ? async ({ event, resolve }) => {
if (event.url.pathname.startsWith("/admin")) {
const session = await event.locals.getSession();
if (!session) {
throw redirect(303, "/login");
}
}
const result = await resolve(event, {
transformPageChunk: ({ html }) => html
});
return result;
} : async ({ event, resolve }) => {
if (event.url.pathname.startsWith("/admin")) {
throw redirect(303, "/login");
}
const result = await resolve(event, {
transformPageChunk: ({ html }) => html
});
return result;
}
);

View File

@ -10,7 +10,7 @@
<div class="flex flex-row items-center gap-2">
<div class="i-ic:outline-bookmarks text-xl -ml-1" />
{#each post.tags as tag}
<a href="/blog/tags/{tag.slug}" class="no-underline rounded-2 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}
</div>
{/if}

View File

@ -3,6 +3,6 @@
</script>
<div class="prose flex flex-col text-justify m-auto">
<img src={data.post.feature_image} alt="{data.post.title} image" class="rounded-2">
<img src={data.post.feature_image} alt="{data.post.title} image" class="rounded">
{@html data.post.html}
</div>

View File

@ -3,7 +3,7 @@
export let isPost: boolean = false;
</script>
<div class="flex flex-col gap-4 p-4 rounded-2 {isPost ? "" : "w-120 bg-secondary"}">
<div class="flex flex-col gap-4 p-4 rounded {isPost ? "" : "w-110 bg-secondary"}">
<slot />
{#if url}

View File

@ -1,8 +1,3 @@
<script lang="ts">
export let hasMt: boolean = false;
export let isHome: boolean = false;
</script>
<div class="flex flex-row flex-wrap gap-10 {hasMt ? "mt-16" : ""} {isHome ? "justify-center" : ""}">
<div class="flex flex-row flex-wrap gap-4">
<slot />
</div>

View File

@ -6,6 +6,6 @@
<div class="flex flex-col gap-4">
{#each items as item}
<a href="/blog/{name}/{item.slug}" class="bg-secondary sm:w-md p-2 rounded-2 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}
</div>

View File

@ -5,7 +5,7 @@
{#if !isPost}
{#if post.feature_image}
<img src={post.feature_image} alt="{post.title} image" class="rounded-2">
<img src={post.feature_image} alt="{post.title} image" class="rounded">
{/if}
<a href="/blog/{post.slug}" class="text-text no-underline hover:underline"><span class="text-xl font-bold">{post.title}</span></a>
{:else}

View File

@ -1,36 +0,0 @@
<script lang="ts">
export let title: string = "";
export let position: string = "";
export let description: string = "";
export let icon: string = "";
export let positionStyles: string = "";
</script>
<div class="bg-secondary rounded-2 p-4 w-[18rem] sm:w-md flex flex-col">
<div class="flex-1 flex flex-row gap-4">
{#if icon}
<div>
<img
src={icon}
class="h-20 rounded-2"
alt="{title} icon"
/>
</div>
{/if}
<div>
<span class="text-2xl font-bold">
{title}
{#if position}
<span>- </span>
<span style={positionStyles}>{position}</span>
{/if}
</span>
{#if description}
<p class="description">{description}</p>
{/if}
</div>
</div>
<slot />
</div>

View File

@ -1,3 +0,0 @@
<div class="flex gap-8 flex-row flex-wrap">
<slot />
</div>

View File

@ -1,49 +0,0 @@
<script lang="ts">
export let url: string = "";
let classes: string = "";
export { classes as class };
</script>
<a
href={url}
class="border-none rounded-2 p-2 cursor-pointer font-primary text-secondary decoration-none w-fit text-xl flex items-center {classes}"
>
<slot />
</a>
<style>
.web,
.email,
.picture,
.pgp,
.link {
@apply bg-alt text-alt-text transition-all duration-250;
}
.web:hover,
.email:hover,
.picture:hover,
.pgp:hover {
@apply bg-accent text-alt;
}
.matrixcolored {
@apply bg-alt text-alt-text;
}
.discordcolored {
@apply bg-[#5865f2] text-white;
}
.gitcolored {
@apply bg-[#f05032] text-white;
}
.githubcolored {
@apply bg-alt text-alt-text;
}
.torcolored {
@apply bg-[#7d4698] text-white;
}
</style>

View File

@ -1,3 +0,0 @@
<div class="flex flex-row flex-wrap gap-2">
<slot />
</div>

View File

@ -1,4 +0,0 @@
export { default as CardOuter } from "./CardOuter.svelte";
export { default as CardInner } from "./CardInner.svelte";
export { default as LinksOuter } from "./LinksOuter.svelte";
export { default as Link } from "./Link.svelte";

View File

@ -1,8 +1,7 @@
<footer class="flex flex-col text-xl 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"
>
<span class="flex flex-row items-center gap-1">Made with <div class="i-simple-icons:svelte text-[#FF3E00]" />SvelteKit <span class="hidden sm:block">-</span></span>
<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">
<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>
<a href="https://github.com/ProjectSegfault/website">Source code</a>
</div>
</footer>
</footer>

View File

@ -1,33 +0,0 @@
<script lang="ts">
import HCaptcha from "svelte-hcaptcha";
import { Note } from "$lib/Form";
let submit = false;
const showSubmitButton = () => {
submit = !submit;
};
export let sitekey: string = "";
</script>
<Note
content="The submit button will be visible when you complete the Captcha."
icon="i-ic:outline-info text-xl"
/>
<HCaptcha
{sitekey}
on:success={showSubmitButton}
/>
<slot />
{#if submit}
<button
type="submit"
value="Submit"
class="form-button"
>
Submit
</button>
{/if}

View File

@ -1,28 +0,0 @@
<script lang="ts">
export let action: string = "";
export let method: string = "";
export let id: string = "";
</script>
<form
{action}
{method}
{id}
class="flex flex-col gap-4 w-fit"
>
<slot />
</form>
<style>
:global(.form-button) {
@apply bg-secondary border-none rounded-2 p-2 cursor-pointer text-text font-primary decoration-none;
}
:global(.form-button:not(select):hover) {
@apply bg-accent decoration-none transition-all duration-500 text-secondary;
}
:global(.form-textbox) {
@apply bg-secondary text-text rounded-2 border-none p-2 font-primary outline-none;
}
</style>

View File

@ -1,51 +0,0 @@
<script lang="ts">
export let inputType: string = "";
export let inputName: string = "";
export let inputPlaceholder: string = "";
export let select: boolean = true;
export let selectType: string = "";
export let input2: boolean = false;
export let input2Type: string = "";
export let input2Name: string = "";
export let input2Placeholder: string = "";
</script>
<div
class="flex items-center flex-row gap-4 children:w-[50%] lt-sm:(flex-col items-start justify-center children:w-[calc(100%-1rem)])"
>
<input
type={inputType}
name={inputName}
class="form-textbox"
placeholder={inputPlaceholder}
required
/>
{#if input2}
<input
type={input2Type}
name={input2Name}
class="form-textbox"
placeholder={input2Placeholder}
required
/>
{/if}
{#if select}
<select
name={selectType}
required
class="form-button"
>
<slot />
</select>
{/if}
</div>
{#if select}
<style>
@media screen and (max-width: 640px) {
div > :nth-child(2) {
width: 100%;
}
}
</style>
{/if}

View File

@ -1,11 +0,0 @@
<script lang="ts">
export let content: string = "";
export let icon: string = "";
</script>
<div class="flex items-center gap-2">
{#if icon}
<div class={icon} />
{/if}
<b>{content}</b>
</div>

View File

@ -1,15 +0,0 @@
<script lang="ts">
export let id: string = "";
export let name: string = "";
export let placeholder: string = "";
</script>
<textarea
{id}
{name}
rows="4"
cols="25"
required
class="form-textbox resize-y"
{placeholder}
/>

View File

@ -1,5 +0,0 @@
export { default as Note } from "./Note.svelte";
export { default as Captcha } from "./Captcha.svelte";
export { default as Form } from "./Form.svelte";
export { default as Meta } from "./Meta.svelte";
export { default as TextArea } from "./TextArea.svelte";

View File

@ -1,20 +1,8 @@
<script lang="ts">
export let title: string = "";
export let description: string = "";
export let marginTop: string = "";
let styles: string = "";
export { styles as style };
</script>
<div
class="flex flex-col items-center justify-center children:(m-4 p-0 text-center)"
style="margin-top: {marginTop}%; {styles}"
>
{#if title}
<h1 class="text-5xl font-800 text-accent">{title}</h1>
{/if}
{#if description}
<p class="text-3xl text-text">{description}</p>
{/if}
<slot />
</div>
<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>
<p class="text-2xl">Open source development and hosted services.</p>
<div class="flex flex-row gap-4">
<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>

View File

@ -1,19 +0,0 @@
<script lang="ts">
export let url: string = "";
export let icon: string = "";
export let title: string = "";
export let bg: string = "";
export let color: string = "";
export let styles: string = "";
</script>
<a
href={url}
class="decoration-none bg-accent px-3 py-2 text-primary rounded-2 transition-filter hover:brightness-125 flex items-center w-fit gap-2"
style="background-color: {bg}; color: {color}; {styles}"
>
{#if icon}
<div class={icon} />
{/if}
{title}
</a>

View File

@ -6,61 +6,51 @@
$: currentPage = $page.url.pathname;
let showMenuButton = false;
$: innerWidth = 0;
let innerWidth: number = 0;
$: isMobile = innerWidth < 1090;
$: showMenuButton = innerWidth < 1090;
$: hasJS = typeof Window !== "undefined";
let menuOpen = false;
$: showMenuButton = hasJS && isMobile;
$: menuOpen = innerWidth > 1090;
$: menuOpen = !hasJS || hasJS && !isMobile;
$: menuOpenMobile = innerWidth < 1090 && menuOpen;
$: menuOpenMobile = isMobile && menuOpen;
let showThemeToggle: boolean = true;
$: showThemeToggle = hasJS;
const toggleMenu = () => {
menuOpen = !menuOpen;
};
const toggleMenu = () => menuOpen = !menuOpen;
const handleNavigation = () => {
if (showMenuButton) {
menuOpen = false;
} else {
menuOpen = true;
}
};
const handleNavigation = () => showMenuButton ? menuOpen = false : menuOpen = true;
const menus = [
{ name: "Instances", url: "/instances" },
{ name: "Donate", url: "/donate" },
// { name: "Pubnix", url: "/pubnix" },
{ name: "Contact us", url: "/contact" },
{ name: "Our team", url: "/team" },
{ name: "Timeline", url: "/timeline" },
{ name: "Contact", url: "/contact" },
{ name: "Team", url: "/team" },
{
name: "Wiki",
url: "https://wiki.projectsegfau.lt/",
external: true
},
{ name: "Blog", url: "/blog" },
{ name: "Legal", url: "/legal" },
{
name: "Status",
url: "https://status.projectsegfau.lt/",
external: true
}
},
{ name: "Legal", url: "/legal" }
];
$: if (typeof Window === "undefined") {
menuOpen = true;
showMenuButton = false;
showThemeToggle = false;
}
</script>
<svelte:window bind:innerWidth />
<nav
class="bg-primary {menuOpenMobile ? "border-none" : "border-b border-b-solid border-b-grey"} flex p-2 flex-col justify-between nav:(flex-row items-center)"
class:hasJSNav={typeof Window !== "undefined"}
class:noJSNav={typeof Window === "undefined"}
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:hasJSNav={hasJS}
class:noJSNav={!hasJS}
>
<div class="flex flex-row items-center justify-between">
<a
@ -85,9 +75,9 @@
{#if menuOpen}
<div
class="links"
class:hasJS={typeof Window !== "undefined"}
class:noJS={typeof Window === "undefined"}
class="links {isMobile ? "!children:py-2" : ""}"
class:hasJS={hasJS}
class:noJS={!hasJS}
transition:slide="{{duration: 300, easing: quintOut }}"
>
{#each menus as { url, name, external }}
@ -134,7 +124,7 @@
}
.links > * {
@apply p-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 cursor-pointer text-text decoration-none transition-color duration-250 text-sm font-500 flex items-center hover\:(text-accent);
}
.icon > span {

View File

@ -17,7 +17,7 @@
<button
on:click={toggle}
class="cursor-pointer flex items-center py-1 px-0 bg-transparent border-0 font-primary color-text"
class="text-text flex items-center text-sm"
>
<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>

8
src/lib/PMargin.svelte Normal file
View File

@ -0,0 +1,8 @@
<script lang="ts">
let classes: string = "";
export { classes as class };
</script>
<p class="my-4 {classes}">
<slot />
</p>

View File

@ -0,0 +1,10 @@
<script>
import { fly } from "svelte/transition";
export let pathname = "";
</script>
{#key pathname}
<div in:fly={{ x: -10, duration: 250, delay: 250 }} out:fly={{ x: 5, duration: 250 }}>
<slot />
</div>
{/key}

View File

@ -1,52 +0,0 @@
@font-face {
font-family: "JetBrains Mono";
src: url("/JetBrainsMono.woff2");
font-display: swap;
}
html, html.light {
--accent: #00a584;
--accent-translucent: #00a58498;
--font-primary: "JetBrains Mono", monospace;
--primary: #ffffff;
--secondary: #eeeeee;
--tertiary: #939393;
--text: #444444;
--grey: #cecece;
--alt: #ddd;
--alt-text: #333;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
html {
--primary: #151515;
--secondary: #252525;
--tertiary: #353535;
--text: #ffffffde;
--grey: #5454547a;
--alt: #333;
--alt-text: #ddd;
color-scheme: dark;
}
}
body {
@apply font-primary bg-primary text-text m-0 flex flex-col relative min-h-screen leading-relaxed transition-all duration-250;
}
::selection {
@apply bg-accentTranslucent;
}
main {
@apply p-4;
}
a {
@apply underline text-accent underline-offset-5 transition-filter duration-250;
}
a:hover {
@apply brightness-125;
}

View File

@ -1,9 +0,0 @@
import { env } from "$env/dynamic/private";
const fetchApi = async (action: string, additional?: string) => {
const data = await fetch("https://blog.projectsegfau.lt/ghost/api/content/" + action + "/?key=" + env.GHOST_API_KEY + "&include=authors,tags&limit=all&formats=html,plaintext" + (additional ? additional : ""));
return data.json();
};
export default fetchApi;

View File

@ -1,3 +0,0 @@
const map = new Map();
export default map;

View File

@ -1,14 +0,0 @@
import { env } from "$env/dynamic/private";
import { building } from "$app/environment";
import { MongoClient } from "mongodb";
import type { Db } from "mongodb";
export let db: Db;
if (!building) {
const client = new MongoClient(env.DB_URL);
await client.connect();
db = client.db("website");
}

View File

@ -1,5 +1,8 @@
<script lang="ts">
<script>
import { page } from '$app/stores';
</script>
<h1>{$page.status}: {$page.error?.message}</h1>
<h1>
{$page.status}
<p class="text-base font-normal mt-4">{$page.error?.message}</p>
</h1>

View File

@ -1,43 +1,30 @@
<script>
import "$lib/app.css";
import Nav from "$lib/Nav.svelte";
import Footer from "$lib/Footer.svelte";
import SvelteSeo from "svelte-seo";
<script lang="ts">
import "uno.css";
import "@unocss/reset/tailwind.css";
import "../app.css";
import Nav from "$lib/Nav/Nav.svelte";
import Footer from "$lib/Footer.svelte";
import { page } from "$app/stores";
import PageTransition from "$lib/PageTransition.svelte";
import type { LayoutData } from "./$types";
export let data: LayoutData;
</script>
<SvelteSeo
title="Project Segfault"
description="Open source development and hosted services."
canonical="https://projectsegfau.lt/"
keywords="projectsegfault, project segfault, privacy services, privacy instances, invidious, nitter, searxng"
openGraph={{
url: "https://projectsegfau.lt/",
title: "Project Segfault",
description: "Open source development and hosted services.",
images: [
{
url: "/ProjectSegfault_Desktop_16-9.png",
width: 850,
height: 650,
alt: "Our banner"
}
]
}}
/>
<svelte:head>
<script
defer
data-domain="projectsegfau.lt"
src="https://analytics.projectsegfau.lt/js/plausible.js"
></script>
<title>{$page.data.title} | Project Segfault {$page.url.pathname.startsWith("/blog") ? "blog" : ""}</title>
{#if $page.data.description}
<meta name="description" content={$page.data.description} />
{/if}
</svelte:head>
<Nav />
<main>
<slot />
</main>
<main class="px-8 mb-8 max-w-90rem m-auto">
<PageTransition pathname={data.pathname}>
<slot />
</PageTransition>
</main>
<Footer />

7
src/routes/+layout.ts Normal file
View File

@ -0,0 +1,7 @@
import type { LayoutLoad } from "./$types";
export const load = (async ({ url: { pathname } }) => {
return {
pathname
}
}) satisfies LayoutLoad

View File

@ -1,21 +1,27 @@
import type { PageServerLoad } from "./$types";
import { marked } from "marked";
import sanitizeHtml from "sanitize-html";
import { db } from "$lib/server/db";
import axios from "axios";
import { Agent } from "https";
import { env } from "$env/dynamic/private";
export const load: PageServerLoad = async () => {
const agent = new Agent({
family: 4
});
const collection = db.collection("announcements");
const data = await collection.find({}, { projection: { _id: 0 } }).toArray();
if (data.length !== 0 || data[0] !== undefined) {
const sanitizedContent = sanitizeHtml(data[0].title)
return {
announcements: data[0],
content: marked(sanitizedContent)
}
export const load = (async () => {
const meta = {
title: "Home",
description: "Open source development and hosted services."
}
};
try {
const res = await axios(env.KUMA_URL, { httpsAgent: agent });
if (res.status === 200) {
return { announcements: res.data, ...meta };
} else {
return { error: true, message: "Error: " + res.status };
}
} catch (err) {
return { error: true, message: "Error: " + err };
}
}) satisfies PageServerLoad;

View File

@ -1,41 +1,52 @@
<script lang="ts">
import SvelteSeo from "svelte-seo";
import Hero from "$lib/Hero.svelte";
import LinkButton from "$lib/LinkButton.svelte";
import Announcements from "./Announcements.svelte";
import sanitizeHtml from "sanitize-html";
import type { PageData } from "./$types";
export let data: PageData;
let description: string = "Open source development and hosted services.";
$: backgroundColor = "#5cdd8b";
$: if (!data.error) {
if (data.announcements.incident) {
if (data.announcements.incident.style === "info") {
backgroundColor = "#0dcaf0";
} else if (data.announcements.incident.style === "warning") {
backgroundColor = "#f8a306";
} else if (data.announcements.incident.style === "danger") {
backgroundColor = "#dc3545";
} else if (data.announcements.incident.style === "primary") {
backgroundColor = "#5cdd8b";
} else if (data.announcements.incident.style === "light") {
backgroundColor = "#f8f9fa";
} else if (data.announcements.incident.style === "dark") {
backgroundColor = "#212529";
}
}
}
</script>
<SvelteSeo
title="Home | Project Segfault"
{description}
/>
<Hero />
<Hero
title="Project Segfault"
{description}
marginTop="4"
>
<div
class="flex flex-col sm:flex-row justify-center items-center gap-4 m-4"
>
<LinkButton
url="/instances"
title="Explore our instances"
icon="i-ic:outline-room-service text-xl"
/>
<LinkButton
url="/donate"
icon="i-ic:outline-attach-money text-xl"
title="Donate"
bg="#F6C915"
color="#151515"
/>
</div>
</Hero>
{#if !data.error}
{#if data.announcements.incident}
<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};">
{#if data.announcements.incident.title}
<span class="text-xl font-semibold">{data.announcements.incident.title}</span>
{/if}
<Announcements {data} />
{#if data.announcements.incident.content}
<p>{@html sanitizeHtml(data.announcements.incident.content.replace(/\n/g, "<br />"))}</p>
{/if}
<span>Created - {data.announcements.incident.createdDate}</span>
{#if data.announcements.incident.lastUpdatedDate}
<span>Updated - {data.announcements.incident.lastUpdatedDate}</span>
{/if}
</div>
</div>
{/if}
{:else}
<p>{data.message}</p>
{/if}

View File

@ -1,96 +0,0 @@
<script lang="ts">
import type { PageData } from "./$types";
export let data: PageData;
let announcements = data.announcements;
import dayjs from "dayjs";
</script>
{#if announcements}
<div class="announcements">
<div class="flex justify-center mt-16">
<div
class="announcement !text-[#252525] p-6 rounded-2 w-fit flex flex-col gap-4"
>
<div
class="flex gap-4 flex-col sm:flex-row border-b-2 border-b-solid p-2 pt-0"
>
{#if announcements.severity === "info"}
<div class="flex items-center gap-2">
<div class="i-ic:outline-info text-xl" />
<span>Info</span>
</div>
{:else if announcements.severity === "low"}
<div class="flex items-center gap-2">
<div class="i-ic:outline-check-circle text-xl" />
<span>Resolved</span>
</div>
{:else if announcements.severity === "medium"}
<div class="flex items-center gap-2">
<div class="i-ic:outline-priority-high text-xl" />
<span>Attention</span>
</div>
{:else if announcements.severity === "high"}
<div class="flex items-center gap-2">
<div class="i-ic:outline-block text-xl" />
<span>Attention</span>
</div>
{/if}
<span class="flex items-center gap-2">
<div class="i-ic:outline-person text-xl" />
{announcements.author}
</span>
<span class="flex items-center gap-2">
<div class="i-ic:outline-calendar-month text-xl" />
{dayjs
.unix(announcements.created)
.format("DD/MM/YYYY HH:mm")}
</span>
</div>
<div class="title">
<div class="prose">
{@html data.content}
</div>
</div>
{#if announcements.link}
<div class="read-more">
<a
href={announcements.link}
class="!text-[#252525]">Read more...</a
>
</div>
{/if}
</div>
</div>
</div>
{#if announcements.severity === "info"}
<style>