This commit is contained in:
u80864958
2025-10-21 08:07:41 +02:00
commit 02918feca1
25 changed files with 2729 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

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

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

16
.prettierrc Normal file
View File

@@ -0,0 +1,16 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/app.css"
}

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

42
package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "arch-repo",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.43.2",
"@sveltejs/vite-plugin-svelte": "^6.2.0",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.18",
"@tailwindcss/vite": "^4.1.13",
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.18.0",
"flowbite-svelte-icons": "^3.0.0",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.14",
"svelte": "^5.39.5",
"svelte-check": "^4.3.2",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"vite": "^7.1.7"
},
"dependencies": {
"@ts-stack/markdown": "^1.5.0",
"monorepo": "github:ts-stack/markdown",
"tailwind-merge": "^3.3.1",
"uuid": "^13.0.0",
"valibot": "^1.1.0"
}
}

1787
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
onlyBuiltDependencies:
- esbuild
- '@tailwindcss/oxide'

127
src/app.css Normal file
View File

@@ -0,0 +1,127 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@plugin 'flowbite/plugin';
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-primary-50: #fff5f2;
--color-primary-100: #fff1ee;
--color-primary-200: #ffe4de;
--color-primary-300: #ffd5cc;
--color-primary-400: #ffbcad;
--color-primary-500: #fe795d;
--color-primary-600: #ef562f;
--color-primary-700: #eb4f27;
--color-primary-800: #cc4522;
--color-primary-900: #a5371b;
--color-secondary-50: #f0f9ff;
--color-secondary-100: #e0f2fe;
--color-secondary-200: #bae6fd;
--color-secondary-300: #7dd3fc;
--color-secondary-400: #38bdf8;
--color-secondary-500: #0ea5e9;
--color-secondary-600: #0284c7;
--color-secondary-700: #0369a1;
--color-secondary-800: #075985;
--color-secondary-900: #0c4a6e;
}
@source "../node_modules/flowbite-svelte/dist";
@source "../node_modules/flowbite-svelte-icons/dist";
@layer base {
/* disable chrome cancel button */
input[type='search']::-webkit-search-cancel-button {
display: none;
}
}
/* Custom heading styles */
@layer components {
h1 {
@apply mb-4 text-4xl font-bold text-gray-900;
}
h2 {
@apply mb-3 text-3xl font-semibold text-gray-800;
}
h3 {
@apply mb-2 text-2xl font-medium text-gray-700;
}
h4 {
@apply mb-2 text-xl font-medium text-gray-600;
}
h5 {
@apply mb-1 text-lg font-medium text-gray-500;
}
h6 {
@apply mb-1 text-base font-medium text-gray-400;
}
}
@layer markdown {
.app-markdown {
h1 {
@apply mt-8 mb-4 text-3xl font-bold;
}
h2 {
@apply mt-6 mb-3 text-2xl font-semibold;
}
h3 {
@apply mt-5 mb-2 text-xl font-semibold;
}
h4,
h5,
h6 {
@apply mt-4 mb-2 text-xl font-medium;
}
a {
@apply text-blue-600 underline transition-colors hover:text-blue-800;
}
ul {
@apply mb-4 list-disc pl-6;
}
ol {
@apply mb-4 list-decimal pl-6;
}
li {
@apply mb-1;
}
table {
@apply my-6 w-full border-collapse shadow-sm;
}
th,
td {
@apply border border-gray-200 px-4 py-2 text-left;
}
th {
@apply font-semibold;
}
/**/
/* tr:nth-child(even) { */
/* @apply bg-gray-50; */
/* } */
blockquote {
@apply rounded-md p-3 italic backdrop-blur-md;
}
p {
@apply mb-2;
}
}
}

13
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
src/app.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,42 @@
import { command, form, query } from '$app/server';
import * as v from 'valibot';
import { artefacts, Status, type Artefact } from './artefact';
import { error } from '@sveltejs/kit';
export const getArtefacts = query(
v.object({
view: v.string(),
tags: v.array(v.string())
}),
({ view, tags }): Artefact[] => {
const a = artefacts.filter(({ views }) => views.includes(view) || view === 'Projekt');
if (tags.length == 0) return a;
return a.filter((a) => {
for (const tag of a.tags) {
if (tags.includes(tag)) return true;
}
return false;
});
}
);
export const updateArtefact = form(
v.object({
uuid: v.string(),
content: v.string(),
status: v.enum(Status)
}),
(a) => {
const i = artefacts.findIndex(({ uuid }) => uuid === a.uuid);
if (i < 0) {
return error(404, 'not exist');
}
artefacts[i] = { ...artefacts[i], status: a.status, content: a.content };
}
);
export const getArtefact = query(v.string(), (uuid): Artefact | undefined => {
return artefacts.find((a) => a.uuid === uuid);
});

316
src/lib/artefact.ts Normal file
View File

@@ -0,0 +1,316 @@
import { v4 as uuidv4 } from 'uuid';
export enum Status {
Fehlt = 'Fehlt',
InBearbeitung = 'In Bearbeitung',
Erledigt = 'Erledigt',
Geprueft = 'Geprüft'
}
// Optional: recreate the array from the enum
export const statusValues: { value: string; name: string }[] = Object.values(Status).map(
(value) => ({
value,
name: value
})
);
export interface Artefact {
uuid: string;
tags: string[];
views: string[];
title: string;
description: string;
content: string; // link to document
status: Status;
}
export let artefacts: Artefact[] = [
{
uuid: uuidv4(),
title: 'Integration Service Desk',
description:
'Befähigen des 1-Level Supports. Beschreibt, wie der Service Desk in den Betrieb integriert wird (Schnittstellen, Prozesse, Eskalationswege)',
views: ['Betrieb', 'Governance'],
tags: ['layer:Application', 'type:Betrieb', 'hermes:Einführung'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'P042 - Informationssicherheits- und Datenschutzkonzept (ISDS)',
description:
'Zentrales Konzept zur Umsetzung der Informationssicherheit und des Datenschutzes im Projekt bzw. System',
views: ['Sicherheit'],
tags: ['layer:Business', 'type:Sicherheit', 'hermes:Konzept'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'BCM-Plan (Business Continuity Management)',
description:
'Beschreibt Massnahmen zur Aufrechterhaltung der kritischen Geschäftsprozesse bei Störungen',
views: ['Sicherheit', 'Architektur'],
tags: ['type:Sicherheit', 'type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'BHB',
description: 'Betriebshandbücher',
views: ['Betrieb', 'Architektur'],
tags: ['type:Betrieb', 'type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'AKP-Architekturkonformitätsprüfung',
description: 'Bewertet, ob ein System die Architekturleitlinien der Organisation erfüllt',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'AKP-Checkliste',
description: 'Enthält Prüfpunkte und Bewertungskriterien für Architekturkonformität',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'SKP Sicherheitskonformitätsprüfung',
description: 'Prüft, ob Sicherheitsmassnahmen und ISB-Vorgaben eingehalten werden',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'SKP-Checkliste',
description: 'Prüfliste mit Bewertungspunkten zur Sicherheitskonformität',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Recovery-Plan',
description:
'Beschreibt Verfahren zur Wiederherstellung von IT-Systemen und Daten nach Ausfällen',
views: ['Sicherheit', 'Betrieb'],
tags: ['type:Sicherheit', 'type:Betrieb'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Si001 - Hi01: Massnahmenumsetzung zum IT-Grundschutz in der BV',
description:
'Dokumentiert die Umsetzung der ISB-Sicherheitsmassnahmen gemäss IT-Grundschutzprofil',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Architekturvision',
description: 'Vermittelt Zielbild, Nutzen und Leitplanken für das Vorhaben',
views: ['Management'],
tags: ['type:Management'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Systemkontext (arc42)',
description: 'Zeigt externe Systeme, Schnittstellen und Abhängigkeiten',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Stakeholderanalyse (arc42)',
description: 'Identifiziert relevante Akteure, Rollen und Interessen',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Architekturkonzept',
description: 'Beschreibt Architekturentscheidungen, Aufbau und Integrationsprinzipien',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Bausteinsicht (arc42)',
description: 'Zeigt Systemkomponenten und deren Beziehungen',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Laufzeitsicht (arc42)',
description: 'Beschreibt Interaktionen und dynamisches Verhalten',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Verteilungssicht (arc42)',
description: 'Zeigt Deployments, Infrastruktur und Umgebungen',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Architekturentscheidungen (ADR)',
description: 'Dokumentiert wesentliche Architekturentscheidungen mit Begründungen',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Datenmodell',
description: 'Beschreibt zentrale Datenobjekte und Relationen',
views: ['Daten'],
tags: ['type:Daten'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Schutzbedarfsanalyse (SchuBAN)',
description: 'Schutzbedarfsanalyse: Definiert Schutzziele und Schutzbedarf gemäss ISB-Vorgaben',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Datenschutzkonzept',
description: 'Beschreibt Umsetzung von DSG/DSV-Anforderungen',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Sicherheitskonzept',
description: 'Beschreibt Sicherheitsmassnahmen auf technischer & organisatorischer Ebene',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Risikomanagement / Risikoanalyse',
description: 'Erfasst Bedrohungen, Eintrittswahrscheinlichkeiten und Massnahmen',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Zugriffs- & Berechtigungskonzept',
description: 'Definiert Rollen, Rechte und Zugriffsebenen',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'IKT-Minimalstandard-Nachweis',
description: 'Belegt die Einhaltung der Minimalstandard-Kontrollen',
views: ['Sicherheit'],
tags: ['type:Sicherheit'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Traceability-Matrix',
description: 'Verknüpft Anforderungen mit Architektur- und Sicherheitsmassnahmen',
views: ['Governance'],
tags: ['type:Governance'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Versionierungsübersicht',
description: 'Hält aktuelle und historische Versionen der Artefakte fest',
views: ['Governance'],
tags: ['type:Governance'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Architektur-Review / Freigabeprotokoll',
description: 'Dokumentiert Review-Resultate und formelle Freigabe',
views: ['Governance'],
tags: ['type:Governance'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Qualitätsanforderungen (arc42)',
description: 'Definiert nicht-funktionale Anforderungen (z. B. Performance, Skalierbarkeit)',
views: ['Architektur'],
tags: ['type:Architektur'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Monitoring- & Betriebsarchitektur',
description: 'Beschreibt Überwachung, Logging, Service KPIs',
views: ['Betrieb'],
tags: ['type:Betrieb'],
content: '',
status: Status.Fehlt
},
{
uuid: uuidv4(),
title: 'Glossar (arc42)',
description: 'Vereinheitlicht Begriffe für alle Artefakte',
views: ['Governance', 'Sicherheit', 'Architektur', 'Daten'],
tags: ['type:Governance', 'type:Sicherheit', 'type:Architektur', 'type:Daten'],
content: '',
status: Status.Fehlt
}
];
export const views: string[] = [
...Array.from(new Set(artefacts.flatMap((a) => a.views))),
'Projekt'
];

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import { statusValues, type Artefact, Status } from '$lib/artefact';
import { Button, Card, Label, Modal, Select } from 'flowbite-svelte';
import MarkdownEditor from './MarkdownEditor.svelte';
import { updateArtefact } from '$lib/artefact.remote';
let { artefact, tagClick = (_) => {} }: { artefact: Artefact; tagClick?: (tag: string) => void } =
$props();
let edit = $state(false);
let editValue: Artefact = $state({
uuid: '',
tags: [],
views: [],
title: '',
description: '',
content: '',
status: Status.Fehlt
});
$effect(() => {
editValue = artefact;
});
function getStatusColor(status: Status): string {
switch (status) {
case 'Fehlt':
return 'bg-red-500'; // Missing
case 'In Bearbeitung':
return 'bg-yellow-400'; // In progress
case 'Erledigt':
return 'bg-green-500'; // Done
case 'Geprüft':
return 'bg-green-800'; // Checked
default:
return 'bg-red-500'; // Fallback
}
}
function tagParts(tag: string): string[] {
const parts = tag.split(':');
if (parts.length < 2) {
return ['', tag];
}
return [parts[0], parts[1]];
}
</script>
<Card class="grid grid-cols-2 gap-5 p-5">
<h1 class="col-span-2">{artefact.title}</h1>
<div class="col-span-2 flex flex-wrap">
{#each artefact.tags as tag}
<button
onclick={() => tagClick(tag)}
class="m-1 flex h-8 items-center overflow-hidden rounded-2xl border-2 border-amber-600 bg-amber-200 text-sm transition hover:bg-amber-300"
>
{#if tagParts(tag)[0] !== ''}
<span
class="flex items-center rounded-l-2xl bg-amber-500 px-3 py-1 font-medium text-amber-900"
>
{tagParts(tag)[0].toUpperCase()}:
</span>
{/if}
<span class="px-3 py-1 text-amber-900">{tagParts(tag)[1]}</span>
</button>
{/each}
</div>
<p class="col-span-2 my-5">{artefact.description}</p>
<p
class={`flex items-center justify-center rounded-3xl p-2 text-white ${getStatusColor(artefact.status)}`}
>
{artefact.status}
</p>
<Button onclick={() => (edit = true)}>Edit</Button>
</Card>
<Modal bind:open={edit}>
<form
{...updateArtefact}
onsubmit={() => {
edit = false;
}}
class="m-8"
>
<input type="hidden" value={editValue.uuid} name="uuid" />
<input type="hidden" value={editValue.content} name="content" />
<div class="my-5">
<Label>Status</Label>
<Select bind:value={editValue.status} items={statusValues} name="status"></Select>
</div>
<div class="my-5">
<Label>content</Label>
<MarkdownEditor bind:value={editValue.content} />
</div>
<Button type="submit">Save</Button>
</form>
</Modal>

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import { getArtefacts } from '$lib/artefact.remote';
import Artefact from '$lib/components/Artefact.svelte';
import { Button, Input } from 'flowbite-svelte';
import { TrashBinOutline } from 'flowbite-svelte-icons';
let { view }: { view: string } = $props();
let tags: string[] = $state([]);
let newTag = $state('');
</script>
<div class="m-5 grid grid-cols-5 gap-5">
{#each tags as tag}
<p class="col-span-4">{tag}</p>
<Button
color="red"
onclick={() => {
tags = tags.filter((t) => t != tag);
}}><TrashBinOutline /></Button
>
{/each}
<Input class="col-span-4" bind:value={newTag}></Input>
<Button
onclick={() => {
tags.push(newTag);
newTag = '';
}}>+</Button
>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{#await getArtefacts({ view, tags }) then artefacts}
{#each artefacts as artefact (artefact.uuid)}
<Artefact {artefact} tagClick={(tag) => tags.push(tag)} />
{/each}
{/await}
</div>

View File

@@ -0,0 +1,46 @@
<script lang="ts">
import { Marked, Renderer } from '@ts-stack/markdown';
import { type ClassNameValue, twMerge } from 'tailwind-merge';
Marked.setOptions({
renderer: new Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false
});
let { src, class: className = '' }: { src: string; class?: ClassNameValue } = $props();
let content: Element[] = $state([]);
const DOM_PARSER = new DOMParser();
$effect(() => {
const s = src;
const parsedMarkdown = DOM_PARSER.parseFromString(Marked.parse(s), 'text/html');
content = [...parsedMarkdown.body.children];
});
</script>
<div class={twMerge('app-markdown', className)}>
{#if content != null}
{#each content as element (element)}
<article>
{#if element.firstChild != null && element.firstChild.nodeName == 'IMG'}
<img
src={(element.firstChild as HTMLImageElement).src}
alt={(element.firstChild as HTMLImageElement).alt}
/>
{:else}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html element.outerHTML}
{/if}
</article>
{/each}
{/if}
</div>
<style>
</style>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import Markdown from './Markdown.svelte';
let {
value = $bindable(),
name
}: { value: string; onsave?: () => Promise<void>; name?: string } = $props();
let area: HTMLTextAreaElement = $state()!;
</script>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div
class="relative min-h-40 w-full rounded-lg border-0 bg-gray-50 text-sm focus:border-1 focus:ring-primary-500 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-700 dark:focus:ring-primary-500"
>
<textarea
bind:value
bind:this={area}
{name}
class="min-h-full w-full rounded-lg border-0 bg-gray-50 p-2.5 dark:bg-gray-700"
></textarea>
</div>
<Markdown src={value} class="overflow-scroll" />
</div>

1
src/lib/index.ts Normal file
View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

26
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,26 @@
<script lang="ts">
import '../app.css';
import { Navbar, NavBrand, NavLi, NavUl, NavHamburger } from 'flowbite-svelte';
let { children } = $props();
</script>
<svelte:head></svelte:head>
<!-- <Navbar> -->
<!-- <NavBrand href="/"> -->
<!-- <span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">ArchRepo</span -->
<!-- > -->
<!-- </NavBrand> -->
<!-- <NavHamburger /> -->
<!-- <NavUl> -->
<!-- <NavLi href="/">Home</NavLi> -->
<!-- <NavLi href="/about">About</NavLi> -->
<!-- <NavLi href="/docs/components/navbar">Navbar</NavLi> -->
<!-- <NavLi href="/pricing">Pricing</NavLi> -->
<!-- <NavLi href="/contact">Contact</NavLi> -->
<!-- </NavUl> -->
<!-- </Navbar> -->
<main class="mx-10 mt-5 xl:mx-44">
{@render children?.()}
</main>

15
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,15 @@
<script lang="ts">
import { views } from '$lib/artefact';
import ArtefactsView from '$lib/components/ArtefactsView.svelte';
import { Tabs, TabItem } from 'flowbite-svelte';
</script>
<h1>Project Overview: TODO app</h1>
<Tabs>
{#each views as view, id}
<TabItem open={id === 1} title={view}>
<ArtefactsView {view} />
</TabItem>
{/each}
</Tabs>

3
static/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:

23
svelte.config.js Normal file
View File

@@ -0,0 +1,23 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
experimental: {
remoteFunctions: true
},
adapter: adapter()
},
compilerOptions: {
experimental: {
async: true
}
}
};
export default config;

19
tsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

7
vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
});