mirror of
https://github.com/ProjectSegfault/website.git
synced 2024-12-06 21:48:57 +05:30
v3
This commit is contained in:
parent
add264a5d6
commit
07c0cc4a69
53
.github/workflows/docker-dev.yml
vendored
53
.github/workflows/docker-dev.yml
vendored
@ -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
|
51
.github/workflows/docker.yml
vendored
51
.github/workflows/docker.yml
vendored
@ -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
3
.gitignore
vendored
@ -6,4 +6,5 @@ node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
package-lock.json
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
@ -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" } }]
|
||||
}
|
||||
|
10
README.md
10
README.md
@ -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 |
|
31
compose.yml
31
compose.yml
@ -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"
|
47
package.json
47
package.json
@ -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
1673
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
77
src/app.css
Normal file
77
src/app.css
Normal 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
17
src/app.d.ts
vendored
@ -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 {};
|
||||
|
10
src/app.html
10
src/app.html
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
@ -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}
|
||||
|
@ -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>
|
@ -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}
|
||||
|
@ -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>
|
@ -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>
|
@ -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}
|
||||
|
@ -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>
|
@ -1,3 +0,0 @@
|
||||
<div class="flex gap-8 flex-row flex-wrap">
|
||||
<slot />
|
||||
</div>
|
@ -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>
|
@ -1,3 +0,0 @@
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<slot />
|
||||
</div>
|
@ -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";
|
@ -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>
|
@ -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}
|
@ -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>
|
@ -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}
|
@ -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>
|
@ -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}
|
||||
/>
|
@ -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";
|
@ -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>
|
@ -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>
|
@ -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 {
|
@ -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
8
src/lib/PMargin.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
let classes: string = "";
|
||||
export { classes as class };
|
||||
</script>
|
||||
|
||||
<p class="my-4 {classes}">
|
||||
<slot />
|
||||
</p>
|
10
src/lib/PageTransition.svelte
Normal file
10
src/lib/PageTransition.svelte
Normal 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}
|
@ -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;
|
||||
}
|
@ -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;
|
@ -1,3 +0,0 @@
|
||||
const map = new Map();
|
||||
|
||||
export default map;
|
@ -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");
|
||||
}
|
@ -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>
|
@ -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
7
src/routes/+layout.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { LayoutLoad } from "./$types";
|
||||
|
||||
export const load = (async ({ url: { pathname } }) => {
|
||||
return {
|
||||
pathname
|
||||
}
|
||||
}) satisfies LayoutLoad
|
@ -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;
|
@ -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}
|
@ -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>
|
||||