Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ src/
│ ├── types/ # TypeScript types
│ ├── utils/ # Shared utilities
│ ├── crypto.ts # Crypto utils
│ ├── loro.ts # Loro CRDT
│ ├── loro.svelte.ts # Loro CRDT
│ ├── schema.ts # Shared Effect Schema
│ └── unawaited.ts # Unawaited promise handler
├── routes/
Expand Down Expand Up @@ -155,7 +155,7 @@ Wrap in `$derived` for reactivity.

```svelte
<script lang="ts">
import { getNote } from "$lib/remote/notes.remote";
import { getNote } from "$lib/remote/notes.remote.ts";
let { noteId } = $props();
let noteQuery = $derived(getNote(noteId));
let note = $derived(await noteQuery);
Expand Down Expand Up @@ -185,8 +185,8 @@ async function handleSubmit() {
In SSR, module state is shared across requests.
**Instead:**

- Use `createContext` for component-scoped state.
- Pass data via `props`.
- Use `createContext` for globally-scoped state.
- Pass state via `props` for component-scoped state.

### Optimistic UI

Expand Down
1 change: 1 addition & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { KnipConfig } from "knip";

export default {
entry: ["./src/instrumentation.server.ts"],
ignoreExportsUsedInFile: {
interface: true,
type: true,
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.0.0",
"dotenv": "^17.2.3",
"drizzle-kit": "1.0.0-beta.2-58a4521",
"eslint": "^9.39.1",
"eslint-plugin-svelte": "^3.13.1",
"globals": "^16.5.0",
"knip": "^5.73.4",
"knip": "https://pkg.pr.new/knip@c0ed40a",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.2",
Expand All @@ -64,21 +65,26 @@
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.39.3",
"@effect/platform": "^0.93.6",
"@kubiks/otel-drizzle": "^2.1.0",
"@lezer/highlight": "^1.2.3",
"@lezer/markdown": "^1.6.1",
"@lucide/svelte": "^0.560.0",
"@node-rs/argon2": "^2.0.2",
"@opentelemetry/auto-instrumentations-node": "^0.67.2",
"@opentelemetry/exporter-trace-otlp-proto": "^0.208.0",
"@opentelemetry/sdk-node": "^0.208.0",
"@prosemark/core": "^0.0.4",
"@prosemark/paste-rich-text": "^0.0.2",
"@prosemark/render-html": "^0.0.5",
"clsx": "^2.1.1",
"daisyui": "5.5.13",
"dotenv": "^17.2.3",
"drizzle-orm": "1.0.0-beta.2-58a4521",
"effect": "^3.19.11",
"fast-diff": "^1.3.0",
"import-in-the-middle": "^2.0.0",
"katex": "^0.16.27",
"loro-crdt": "^1.10.3",
"runed": "^0.37.0",
"svelte": "https://pkg.pr.new/svelte@17335",
"typescript-svelte-plugin": "^0.3.50"
},
Expand Down
1,688 changes: 1,672 additions & 16 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Handle } from "@sveltejs/kit";
import * as auth from "$lib/server/auth";
import type { Handle } from "@sveltejs/kit";

const handleAuth: Handle = async ({ event, resolve }) => {
const sessionToken = event.cookies.get(auth.sessionCookieName);
Expand Down Expand Up @@ -39,6 +39,14 @@ const handleAuth: Handle = async ({ event, resolve }) => {
}
}

// Redirect to home if accessing auth routes while logged in
if (event.route.id?.startsWith("/(auth)")) {
return new Response("Redirect", {
status: 303,
headers: { Location: "/" },
});
}

event.locals.user = authData.user;
event.locals.session = authData.session;
return resolve(event);
Expand Down
16 changes: 16 additions & 0 deletions src/instrumentation.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { createAddHookMessageChannel } from "import-in-the-middle";
import { register } from "node:module";

const { registerOptions } = createAddHookMessageChannel();
register("import-in-the-middle/hook.mjs", import.meta.url, registerOptions);

const sdk = new NodeSDK({
serviceName: "test-sveltekit-tracing",
traceExporter: new OTLPTraceExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
8 changes: 7 additions & 1 deletion src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { resolve } from "$app/paths";
import { page } from "$app/state";
import { encryptKeyForUser, generateNoteKey } from "$lib/crypto.ts";
import { getEncryptedSnapshot } from "$lib/loro.svelte.ts";
import { logout } from "$lib/remote/accounts.remote.ts";
import {
createNote,
Expand All @@ -23,6 +24,7 @@
Plus,
Trash2,
} from "@lucide/svelte";
import { LoroDoc } from "loro-crdt";
import { onMount } from "svelte";
import { SvelteSet } from "svelte/reactivity";
import ProfilePicture from "./ProfilePicture.svelte";
Expand Down Expand Up @@ -181,13 +183,17 @@
const noteKey = await generateNoteKey();

// Encrypt note key with user's public key
const encryptedKey = await encryptKeyForUser(noteKey, publicKey);
const [encryptedKey, encryptedSnapshot] = await Promise.all([
encryptKeyForUser(noteKey, publicKey),
getEncryptedSnapshot(new LoroDoc(), noteKey),
]);

const newNote = await createNote({
title,
encryptedKey,
parentId,
isFolder,
encryptedSnapshot,
}).updates(
// TODO: add optimistic update.
getNotes(),
Expand Down
30 changes: 15 additions & 15 deletions src/lib/components/TreeItem.svelte
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
<script lang="ts">
import { resolve } from "$app/paths";
import { page } from "$app/state";
import {
draggable,
dropTargetForElements,
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
getNotes,
reorderNotes,
updateNote,
} from "$lib/remote/notes.remote.ts";
import type { NoteOrFolder } from "$lib/schema.ts";
import { findNode, type TreeNode } from "$lib/utils/tree.ts";
import {
attachClosestEdge,
extractClosestEdge,
type Edge,
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
import { onMount } from "svelte";
import { slide } from "svelte/transition";
import { type TreeNode, findNode } from "$lib/utils/tree.ts";
import type { NoteOrFolder } from "$lib/schema.ts";
import {
updateNote,
reorderNotes,
getNotes,
} from "$lib/remote/notes.remote.ts";
import Self from "./TreeItem.svelte";
draggable,
dropTargetForElements,
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import {
ChevronRight,
FileText,
FolderOpen,
FolderClosed,
FolderOpen,
} from "@lucide/svelte";
import { clsx } from "clsx";
import { resolve } from "$app/paths";
import { page } from "$app/state";
import { onMount } from "svelte";
import { slide } from "svelte/transition";
import Self from "./TreeItem.svelte";

interface Props {
item: TreeNode;
Expand Down
22 changes: 12 additions & 10 deletions src/lib/components/codemirror/Editor.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import "katex/dist/katex.min.css";

import { wikilinksExtension } from "$lib/editor/wikilinks.ts";
import { getNotes } from "$lib/remote/notes.remote.ts";
import { EditorView } from "@codemirror/view";
import {
Bold,
Expand All @@ -16,33 +18,32 @@
} from "@lucide/svelte";
import Codemirror from "./Codemirror.svelte";
import {
coreExtensions,
boldCommand,
italicCommand,
strikethroughCommand,
bulletListCommand,
codeCommand,
linkCommand,
coreExtensions,
heading1Command,
heading2Command,
heading3Command,
bulletListCommand,
italicCommand,
linkCommand,
orderedListCommand,
strikethroughCommand,
} from "./Editor.ts";
import Toolbar from "./Toolbar.svelte";
import { wikilinksExtension } from "$lib/editor/wikilinks.ts";
import type { NoteOrFolder } from "$lib/schema.ts";

interface Props {
content: string;
onchange: (newContent: string) => void;
notesList?: NoteOrFolder[];
}

let { content, onchange, notesList = [] }: Props = $props();
let { content, onchange }: Props = $props();

// svelte-ignore non_reactive_update
let editorView: EditorView;

const notesListQuery = $derived(getNotes());

/** Custom theme */
const editorTheme = EditorView.theme({
"&": {
Expand Down Expand Up @@ -100,7 +101,7 @@

const extensions = $derived([
coreExtensions,
wikilinksExtension.of({ notesList }),
wikilinksExtension.of({ notesList: await notesListQuery }),
// Update listener
EditorView.updateListener.of((update) => {
if (update.docChanged) {
Expand All @@ -117,6 +118,7 @@

// Update content if it changes externally (from Loro)
$effect(() => {
if (!(editorView as EditorView | undefined)) return;
if (content !== editorView.state.doc.toString()) {
console.debug("[Prosemark] External content update");
editorView.dispatch({
Expand Down
13 changes: 7 additions & 6 deletions src/lib/editor/wikilinks.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { goto } from "$app/navigation";
import { resolve } from "$app/paths";
import type { NoteOrFolder } from "$lib/schema.ts";
import type { RangeSet } from "@codemirror/state";
import {
Decoration,
ViewPlugin,
MatchDecorator,
EditorView,
MatchDecorator,
ViewPlugin,
WidgetType,
type ViewUpdate,
} from "@codemirror/view";
import type { RangeSet } from "@codemirror/state";
import { goto } from "$app/navigation";
import { resolve } from "$app/paths";
import type { NoteOrFolder } from "$lib/schema.ts";

class WikilinkWidget extends WidgetType {
title: string;
Expand All @@ -32,6 +32,7 @@ class WikilinkWidget extends WidgetType {
if (targetNote) {
goto(resolve("/notes/[id]", { id: targetNote.id }));
} else {
// TODO: Show a toast notification?
console.debug("Note not found:", this.title);
// Optional: Create note if not found?
}
Expand Down
Loading
Loading