Skip to content

Commit

Permalink
feat: finish off minimal backend Loro integration.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Jan 25, 2025
1 parent 3872c4a commit 975df10
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 11 deletions.
3 changes: 2 additions & 1 deletion src/lib/pages/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ async function get(link: ExactLink): Promise<PageData | undefined> {

async function getLoroSnapshot(link: ExactLink): Promise<Uint8Array | undefined> {
const ent = await leafClient.get_components(link, LoroCommonMark);
return ent?.get(LoroCommonMark)?.value;
const v = ent?.get(LoroCommonMark)?.value;
return v ? new Uint8Array(v) : undefined;
}

async function save(
Expand Down
6 changes: 3 additions & 3 deletions src/routes/(app)/[username]/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const load: PageServerLoad = async ({ params }): Promise<{ page: Page }>

export const actions = {
default: async ({ request, params, url, fetch }) => {
const { sessionInfo } = await getSession(fetch, request);
if (!sessionInfo) return redirect(302, `/login?to=${url}`);

const fullUsername = usernames.fullDomain(params.username);
const subspace = await usernames.getSubspace(fullUsername);
if (!subspace) return error(404, `User not found: ${fullUsername}`);
Expand All @@ -35,9 +38,6 @@ export const actions = {
const oldEnt = await leafClient.get_components(oldPageLink, WeirdWikiPage);
const isWikiPage = !!oldEnt?.get(WeirdWikiPage);

const { sessionInfo } = await getSession(fetch, request);
if (!sessionInfo) return redirect(302, `/login?to=${url}`);

const editorIsOwner = sessionInfo.user_id == (await usernames.getRauthyId(fullUsername));

// If this isn't a wiki page, we need to make sure that only the author can edit the page.
Expand Down
13 changes: 9 additions & 4 deletions src/routes/(app)/[username]/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import type { Page, PageSaveReq } from '$lib/pages/types';
import { LoroDoc } from 'loro-crdt';
import base64 from 'base64-js';
import { checkResponse } from '$lib/utils/http';
const { data, form }: { data: PageData; form: ActionData } = $props();
let editingState: { editing: boolean; page: Page } = $state({
editing: false,
page: data.page
});
let previousLoroSnapshot = $state(undefined) as Uint8Array | undefined;
const slugifiedSlug = $derived(
slugify(editingState.page.slug || editingState.page.name || 'untitled', {
Expand All @@ -32,8 +34,11 @@
})
);
function startEdit() {
async function startEdit() {
if (data.profileMatchesUserSession || data.page.wiki) {
const resp = await fetch(`/${$page.params.username}/${$page.params.slug}/loroSnapshot`);
await checkResponse(resp);
previousLoroSnapshot = new Uint8Array(await resp.arrayBuffer());
editingState.page = data.page;
editingState.editing = true;
}
Expand All @@ -55,11 +60,11 @@
let formDataInput: HTMLInputElement = $state(undefined) as unknown as HTMLInputElement;
async function handleSubmit() {
const doc = new LoroDoc();
doc.setRecordTimestamp(true);
if (previousLoroSnapshot) doc.import(previousLoroSnapshot);
const content = doc.getText('content');
content.delete(0, 10e30);
content.delete(0, content.length);
content.insert(0, editingState.page.markdown);
console.log('markdown', editingState.page.markdown);
console.log('doc', content.toString());
const out: PageSaveReq = {
name: editingState.page.name,
slug: slugifiedSlug,
Expand Down
12 changes: 9 additions & 3 deletions src/routes/(app)/[username]/[slug]/loroSnapshot/+server.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { subspace_link } from '$lib/leaf';
import { pages } from '$lib/pages/server';
import { getSession } from '$lib/rauthy/server';
import { usernames } from '$lib/usernames';
import { error, type RequestHandler } from '@sveltejs/kit';

export const GET: RequestHandler = async ({ request, fetch, params, url }) => {
const fullUsername = usernames.fullDomain(params.username!);
export const GET: RequestHandler = async ({ params, request, fetch }) => {
const { sessionInfo } = await getSession(fetch, request);
if (!sessionInfo) return error(403, `Not logged in`);

const fullUsername = await usernames.getByRauthyId(sessionInfo.user_id);
if (!fullUsername) return error(404, `User not found`);
const subspace = await usernames.getSubspace(fullUsername);
if (!subspace) return error(404, `User not found: ${fullUsername}`);
const pageLink = subspace_link(subspace, params.slug!);

const snapshot = await pages.getLoroSnapshot(pageLink);
if (!snapshot) return error(404, `Snapshot not found`);

return new Response();
return new Response(snapshot);
};
103 changes: 103 additions & 0 deletions src/routes/(app)/[username]/[slug]/revisions/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script lang="ts">
import { page } from '$app/stores';
import { checkResponse } from '$lib/utils/http';
import { onMount } from 'svelte';
import { ProgressRadial, RangeSlider } from '@skeletonlabs/skeleton';
import { LoroDoc } from 'loro-crdt';
import { renderMarkdownSanitized } from '$lib/utils/markdown';
let showVersion = $state(0);
let snapshot = $state(undefined) as Uint8Array | undefined;
let markdown = $state('');
let loroDoc = $derived.by(() => {
if (snapshot) {
const doc = new LoroDoc();
doc.import(snapshot);
return doc;
}
});
let changes = $derived.by(() => {
if (loroDoc) {
return loroDoc.getAllChanges();
}
return new Map() as ReturnType<LoroDoc['getAllChanges']>;
});
let changeTimestamps = $derived.by(() => {
const timestamps = [...changes.values()].flat().map((x) => x.timestamp);
timestamps.sort((a, b) => a - b);
return timestamps;
});
const getFrontierForTimestamp = (ts: number): { peer: string; counter: number }[] => {
const frontiers = [] as { peer: string; counter: number }[];
changes.forEach((changes, peer) => {
let counter = 0;
for (const change of changes) {
if (change.timestamp <= ts) {
counter = Math.max(counter, change.counter + change.length - 1);
}
}
if (counter > 0) {
frontiers.push({ counter, peer });
}
});
return frontiers;
};
$effect(() => {
if (loroDoc) {
showVersion = changeTimestamps.length;
}
});
$effect(() => {
if (loroDoc) {
const ts = changeTimestamps[showVersion - 1];
const frontiers = getFrontierForTimestamp(ts);
loroDoc.checkout(frontiers as any);
markdown = loroDoc.getText('content').toString();
}
});
onMount(async () => {
const resp = await fetch(`/${$page.params.username}/${$page.params.slug}/loroSnapshot`);
await checkResponse(resp);
snapshot = new Uint8Array(await resp.arrayBuffer());
});
</script>

<main class="mx-4 flex w-full flex-col items-center px-2 font-spacemono">
<div
class="card relative m-4 mt-12 flex w-full max-w-[1000px] flex-col justify-center gap-4 rounded-xl p-8 text-xl"
>
<h1 class="text-center text-2xl font-bold">
Revisions for <a
class="underline underline-offset-4"
href={`/${$page.params.username}/${$page.params.slug}`}><code>{$page.params.slug}</code></a
>
</h1>

{#if loroDoc}
<RangeSlider
name="range-slider"
bind:value={showVersion}
min={1}
max={changeTimestamps.length}
step={1}
ticked
></RangeSlider>

<div class="flex flex-col gap-8">
<div
class="prose relative mx-auto w-full max-w-[1000px] pt-4 dark:prose-invert prose-a:text-blue-400"
>
{@html renderMarkdownSanitized(markdown)}
</div>
</div>
{:else}
<div class="m-auto p-20">
<ProgressRadial meter="stroke-primary-500" track="stroke-primary-500/30" />
</div>
{/if}
</div>
</main>
1 change: 1 addition & 0 deletions src/routes/(app)/[username]/new/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
let formDataInput: HTMLInputElement;
function handleSubmit(_e: SubmitEvent) {
const doc = new LoroDoc();
doc.setRecordTimestamp(true);
const content = doc.getText('content');
content.delete(0, 10e30);
content.insert(0, page.markdown);
Expand Down

0 comments on commit 975df10

Please sign in to comment.