Skip to content

Commit

Permalink
feat: add revisions to pages.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Oct 23, 2024
1 parent d10c843 commit 41625eb
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 39 deletions.
6 changes: 4 additions & 2 deletions leaf/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const SubspaceSecretKeySchema = SubspaceIdSchema;
export type PathSegment =
| { Null: Unit }
| { Bool: boolean }
| { Uint: number }
| { Int: number }
| { Uint: bigint | number }
| { Int: bigint | number }
| { String: string }
| { Bytes: number[] };
export const PathSegmentSchema = BorshSchema.Enum({
Expand All @@ -53,6 +53,8 @@ export function formatEntityPath(p: EntityPath): string {
s += `/"${segment.String}"`;
} else if ('Bytes' in segment) {
s += `/base32:${base32Encode(new Uint8Array(segment.Bytes))}`;
} else if ('Uint' in segment) {
s += `/Uint:${segment.Uint.toString()}`;
} else {
throw 'TODO: implement formatting for other path segment types.';
}
Expand Down
10 changes: 7 additions & 3 deletions src/lib/leaf/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
WebLinks,
WeirdCustomDomain,
WeirdPubpageTheme,
WeirdWikiPage
WeirdWikiPage,
WeirdWikiRevisionAuthor
} from './profile';

/** The Leaf RPC client used to connect to our backend data server. */
Expand Down Expand Up @@ -79,6 +80,7 @@ export type KnownComponents = {
weirdCustomDomain?: WeirdCustomDomain['value'];
commonmark?: CommonMark['value'];
weirdWikiPage?: WeirdWikiPage['value'];
weirdWikiRevisionAuthor?: WeirdWikiRevisionAuthor['value'];
};

export async function loadKnownComponents(link: ExactLink): Promise<KnownComponents | undefined> {
Expand All @@ -92,7 +94,8 @@ export async function loadKnownComponents(link: ExactLink): Promise<KnownCompone
WeirdPubpageTheme,
WeirdCustomDomain,
CommonMark,
WeirdWikiPage
WeirdWikiPage,
WeirdWikiRevisionAuthor
);

if (ent) {
Expand All @@ -105,7 +108,8 @@ export async function loadKnownComponents(link: ExactLink): Promise<KnownCompone
weirdPubpageTheme: ent.get(WeirdPubpageTheme)?.value,
weirdCustomDomain: ent.get(WeirdCustomDomain)?.value,
commonmark: ent.get(CommonMark)?.value,
weirdWikiPage: ent.get(WeirdWikiPage)?.value
weirdWikiPage: ent.get(WeirdWikiPage)?.value,
weirdWikiRevisionAuthor: ent.get(WeirdWikiRevisionAuthor)?.value
};
} else {
return;
Expand Down
31 changes: 13 additions & 18 deletions src/lib/leaf/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,22 @@ should be editable by everybody.`)
}
}

export class CommonMarkRevisions extends Component {
value: { authorId: number[]; date: bigint; markup: string }[];
constructor(value: CommonMarkRevisions['value']) {
export class WeirdWikiRevisionAuthor extends Component {
value: string;
constructor(userId: string) {
super();
this.value = value;
this.value = userId;
}
static componentName(): string {
return 'CommonMarkRevisions';
return 'WeirdWikiRevisionAuthor';
}
static borshSchema(): BorshSchema {
return BorshSchema.Vec(
BorshSchema.Struct({
authorId: BorshSchema.Vec(BorshSchema.u8),
date: BorshSchema.u64,
markup: BorshSchema.String
})
);
return BorshSchema.String;
}
static specification(): Component[] {
return [
new CommonMark(`The list of revisions of the \`CommonMark\` component, and the ID
of the author that created each revision, and the date that the revision was made.
The revisions should be listed from oldest to newest.`)
new CommonMark(`Component containing the Weird user ID of the author that created
a revision on a wiki page.`)
];
}
}
Expand Down Expand Up @@ -209,11 +201,14 @@ export class WeirdCustomDomain extends Component {
/**
* Append a string subpath to the provided link
*/
export function appendSubpath(link: ExactLink, ...pathSegments: string[]): ExactLink {
export function appendSubpath(
link: ExactLink,
...pathSegments: (string | PathSegment)[]
): ExactLink {
return {
namespace: link.namespace,
subspace: link.subspace,
path: [...link.path, ...pathSegments.map((x) => ({ String: x }))]
path: [...link.path, ...pathSegments.map((x) => (typeof x == 'string' ? { String: x } : x))]
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/lib/utils/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function dateToUnixTimestamp(date: Date): bigint {
return BigInt(Math.round((date?.getTime() || Date.now()) / 1000));
}

export function dateFromUnixTimestamp(timestamp: number | bigint): Date {
return new Date(Number(timestamp) * 1000);
}
11 changes: 9 additions & 2 deletions src/routes/(app)/[username]/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { Actions, PageServerLoad } from './$types';
import {
WebLinks,
WeirdWikiPage,
WeirdWikiRevisionAuthor,
appendSubpath,
profileLinkById,
profileLinkByUsername
} from '$lib/leaf/profile';
import { error, fail, redirect } from '@sveltejs/kit';
Expand All @@ -13,6 +13,7 @@ import { leafClient } from '$lib/leaf';
import { CommonMark, Name } from 'leaf-proto/components';
import { Page } from '../types';
import { getSession } from '$lib/rauthy/server';
import { dateToUnixTimestamp } from '$lib/utils/time';

export const load: PageServerLoad = async ({ params }): Promise<{ page: Page }> => {
const username = parseUsername(params.username);
Expand Down Expand Up @@ -92,17 +93,23 @@ export const actions = {
newSlug = editorIsOwner ? data.slug : params.slug;

const pageLink = appendSubpath(profileLink, newSlug);
const revisionLink = appendSubpath(pageLink, { Uint: dateToUnixTimestamp(new Date()) });

if (data.slug != params.slug) {
await leafClient.del_entity(oldPageLink);
}

await leafClient.update_components(pageLink, [
const components = [
new Name(data.display_name),
data.markdown.length > 0 ? new CommonMark(data.markdown) : CommonMark,
data.links.length > 0 ? new WebLinks(data.links) : WebLinks,
// non-owners are not allowed to change the wiki page status
(editorIsOwner ? data.wiki : isWikiPage) ? new WeirdWikiPage() : WeirdWikiPage
];
await leafClient.update_components(pageLink, components);
await leafClient.update_components(revisionLink, [
...components,
new WeirdWikiRevisionAuthor(sessionInfo.user_id)
]);
} catch (e: any) {
return fail(500, { error: JSON.stringify(e) });
Expand Down
10 changes: 9 additions & 1 deletion src/routes/(app)/[username]/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,16 @@

<div class="text-center">
By <a href={`/${$page.params.username}`} class="text-blue-300 underline underline-offset-4">
{data.profile.display_name}
{data.profile.display_name}.
</a>
{#if data.page.wiki}
Wiki Page.
{/if}
See
<a
class="text-blue-300 underline underline-offset-4"
href={`/${$page.params.username}/${$page.params.slug}/revisions`}>Revisions</a
>.
</div>

{#if editingState.editing}
Expand Down
38 changes: 38 additions & 0 deletions src/routes/(app)/[username]/[slug]/revisions/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { PageServerLoad } from './$types';
import { appendSubpath, profileLinkByUsername } from '$lib/leaf/profile';
import { error, redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/public';
import { parseUsername } from '$lib/utils/username';
import { leafClient } from '$lib/leaf';

export const load: PageServerLoad = async ({
params
}): Promise<{ revisions: (number | bigint)[] }> => {
const username = parseUsername(params.username);
if (username.domain == env.PUBLIC_DOMAIN) {
return redirect(302, `/${username.name}/${params.slug}`);
}
const fullUsername = `${username.name}@${username.domain || env.PUBLIC_DOMAIN}`;
const profileLink = await profileLinkByUsername(fullUsername);

if (!profileLink) return error(404, `User not found: ${fullUsername}`);
const pageLink = appendSubpath(profileLink, params.slug);

const links = await leafClient.list_entities(pageLink);
const revisions = [];
for (const link of links) {
const last = link.path[link.path.length - 1];
if ('Uint' in last) {
revisions.push(last.Uint);
}
}

revisions.sort();
// Remove the last revision because it's the same as the current one
revisions.pop();
revisions.reverse();

return {
revisions
};
};
40 changes: 40 additions & 0 deletions src/routes/(app)/[username]/[slug]/revisions/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import type { PageData } from './$types';
import { page } from '$app/stores';
import { dateFromUnixTimestamp } from '$lib/utils/time';
const { data }: { data: PageData } = $props();
</script>

<main class="flex flex-col items-center">
<h1 class="mb-2 mt-3 text-4xl font-bold">Revisions</h1>

<h2 class="font-mono">
{$page.params.username} / {$page.params.slug}
</h2>

<ul class="my-5 flex flex-col items-center gap-3">
<li>
<a class="variant-ghost btn" href={`/${$page.params.username}/${$page.params.slug}`}>
Current
</a>
</li>
{#each data.revisions as revision}
<li>
<a
class="variant-ghost btn"
href={`/${$page.params.username}/${$page.params.slug}/revisions/${revision}`}
>
{new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(dateFromUnixTimestamp(revision))}
</a>
</li>
{/each}
</ul>
</main>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { Actions, PageServerLoad } from './$types';
import {
Username,
WebLinks,
WeirdWikiPage,
WeirdWikiRevisionAuthor,
appendSubpath,
getProfileById,
profileLinkById,
profileLinkByUsername
} from '$lib/leaf/profile';
import { error, fail, redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/public';
import { parseUsername } from '$lib/utils/username';
import { leafClient } from '$lib/leaf';
import { CommonMark, Name } from 'leaf-proto/components';
import { Page } from '../../../types';

export const load: PageServerLoad = async ({
params
}): Promise<{ page: Page; revisionAuthor: string }> => {
const username = parseUsername(params.username);
if (username.domain == env.PUBLIC_DOMAIN) {
return redirect(302, `/${username.name}/${params.slug}`);
}
const fullUsername = `${username.name}@${username.domain || env.PUBLIC_DOMAIN}`;
const profileLink = await profileLinkByUsername(fullUsername);

if (!profileLink) return error(404, `User not found: ${fullUsername}`);
const revisionLink = appendSubpath(profileLink, params.slug, { Uint: BigInt(params.revision!) });

const ent = await leafClient.get_components(
revisionLink,
CommonMark,
WebLinks,
Name,
WeirdWikiRevisionAuthor
);
if (!ent) return error(404, 'Revision not found');

let display_name = ent.get(Name)?.value;
let links = ent.get(WebLinks)?.value;

const commonMark = ent.get(CommonMark)?.value;

if (!display_name) {
display_name = env.PUBLIC_INSTANCE_NAME;
}

const authorId = ent.get(WeirdWikiRevisionAuthor)?.value;
const revisionAuthor =
(await (async () => {
if (!authorId) return;
const profileLink = profileLinkById(authorId);
const ent = await leafClient.get_components(profileLink, Username);
const username = ent?.get(Username)?.value;
return username;
})()) || '';

return {
page: {
slug: params.slug,
display_name,
markdown: commonMark || '',
links: links || [],
wiki: false
},
revisionAuthor
};
};
Loading

0 comments on commit 41625eb

Please sign in to comment.