Skip to content

Commit

Permalink
Fix SPA links for per-file passphrases & fix for in-page navigation n…
Browse files Browse the repository at this point in the history
…ot remounting
  • Loading branch information
spearcat committed Dec 5, 2024
1 parent 21547be commit 9a5721f
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 37 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@craftamap/esbuild-plugin-html": "^0.8.0",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tsconfig/svelte": "^5.0.4",
"@types/browser-util-inspect": "^0.2.4",
"@types/child-process-promise": "^2.2.6",
"@types/express": "^5.0.0",
"@types/mime-db": "^1.43.5",
Expand Down Expand Up @@ -61,6 +62,7 @@
"base32-decode": "^1.0.0",
"base32-encode": "^2.0.0",
"base85": "^3.1.0",
"browser-util-inspect": "^0.2.0",
"buffer": "^6.0.3",
"builtin-modules": "^4.0.0",
"cbor-x": "^1.6.0",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion spa/src/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ApiClient {
fileLastCreatedOrModified: new Date(file.fileLastCreatedOrModified),
recordCreatedAt: new Date(file.recordCreatedAt),
referencedFilePassphrases: file.referencedFilePassphrases
? decodeCbor(await decryptInlineData(file.referencedFilePassphrases, passphrase)) as Record<string, string>
? decodeCbor(await decryptInlineData(file.referencedFilePassphrases, passphrase)) as Record<string, [rkey: string, passphrase: string]>
: undefined
};
} else {
Expand Down
2 changes: 1 addition & 1 deletion spa/src/markdown-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function makeUrl(
if (passphrase) {
if (!referencedFilePassphrases || !((filePath + '.md') in referencedFilePassphrases)) {
console.log('unresolved link:', filePath);
return filePath;
return `#/${route}/${handle}/${getPublicFileRkey({ path: filePath, vaultName: currentVault })}`;
}

// TODO filePath or realPath here? is this folder handling correct?
Expand Down
2 changes: 1 addition & 1 deletion spa/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import NotFound from './routes/NotFound.svelte';

export default {
'/': Home,
'/private-page/:handle/:rkey/:passphrase': Page,
'/private-page/:handle/:rkey/:passphrase?': Page,
'/page/:handle/:rkey': Page,
// The catch-all route must always be last
'*': NotFound
Expand Down
96 changes: 62 additions & 34 deletions spa/src/routes/Page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,52 @@
import { ApiClient } from "../api-client";
import path from 'path-browserify';
import { makeUrl, markdownRender } from "../markdown-renderer";
import { push, pop, replace } from 'svelte-spa-router'
import { onMount } from "svelte";
import inspect from 'browser-util-inspect';
export let params: { handle: string; rkey: string; passphrase?: string };
const handle = params.handle;
const rkey = params.rkey;
const passphrase = params.passphrase;
const route = location.hash.startsWith('#/page/') ? 'page' : 'private-page';
let handle: string;
let rkey: string;
let passphrase: string | undefined;
let route: string;
// svelte-spa-router does not re-mount the component when parameters change, so we need to use reactivity
$: {
handle = params.handle;
rkey = params.rkey;
passphrase = params.passphrase;
route = location.hash.startsWith('#/page/') ? 'page' : 'private-page';
}
let htmlElement: HTMLElement;
let title: string | undefined;
let file: Awaited<ReturnType<ApiClient['getAndDecryptFile']>> | undefined = undefined;
let textContent: string | undefined;
let dom: DocumentFragment;
const filePromise = ApiClient.create(handle)
.then(client => client.getAndDecryptFile(rkey, passphrase))
.then(afile => file = afile)
.catch(err => console.error(err));
let filePromise: ReturnType<ApiClient['getAndDecryptFile']>;
$: {
filePromise = ApiClient.create(params.handle)
.then(client => client.getAndDecryptFile(params.rkey, params.passphrase))
.then(result => file = result)
filePromise
.catch(err => console.error(err));
}
// add this as an onclick to anchor events to navigate to the page via the router instead
function navigate(event: MouseEvent) {
if (event.button !== 0) return;
const href = (event.currentTarget as HTMLAnchorElement).href;
if (href.includes('#/')) {
event.preventDefault();
push(href.slice(href.indexOf('#/')));
}
}
$: {
textContent = typeof file?.contents === 'string'
Expand Down Expand Up @@ -49,8 +77,8 @@
console.log(anchor, href, realPath);
// TODO passphrase support here
anchor.setAttribute('href', realPath);
(anchor as HTMLAnchorElement).addEventListener('click', navigate);
}
}
}
Expand All @@ -62,9 +90,15 @@
href => makeUrl(file!.filePath, file!.vaultName, route, handle, decodeURI(href), passphrase, file!.referencedFilePassphrases)
);
for (const anchor of rendered.element.querySelectorAll('a')) {
if (anchor.href.includes('#')) {
anchor.addEventListener('click', navigate);
}
}
if (rendered.frontmatterYaml) {
const pre = document.createElement('pre');
pre.style.overflowX = 'scroll';
pre.style.overflowX = 'auto';
pre.append(rendered.frontmatterYaml);
htmlElement.append(pre);
}
Expand All @@ -73,34 +107,28 @@
}
</script>

<div class="container">
{#await filePromise}
<p>Loading file from repo @{handle}</p>
{:then file}
{#if 'html' in file && file.html}
<div class="obsidian-root">
<h1>{path.basename(file.filePath, '.md')}</h1>
{#await filePromise}
<p>Loading file from repo <a href="https://bsky.app/profile/{handle}">@{handle}</a></p>
{:then file}
{#if 'html' in file && file.html}
<div class="obsidian-root">
<h1>{path.basename(file.filePath, '.md')}</h1>
<div bind:this={htmlElement} />
</div>
{:else if textContent}
<div class="obsidian-root">
<h1>{path.basename(file.filePath, '.md')}</h1>
<div class="text-content">
<div bind:this={htmlElement} />
</div>
{:else if textContent}
<div class="obsidian-root">
<h1>{path.basename(file.filePath, '.md')}</h1>
<div class="text-content">
<div bind:this={htmlElement} />
</div>
</div>
{/if}
{:catch err}
<p>File load error:</p>
<pre style="overflow-x: scroll;">{err}</pre>
{/await}
</div>
</div>
{/if}
{:catch err}
<p>File load error:</p>
<pre style="overflow-x: auto;">{inspect(err)}</pre>
{/await}

<style lang="scss">
body {
background-color: var(--bg-color);
}
.container {
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Microsoft YaHei Light", sans-serif;
}
Expand Down

0 comments on commit 9a5721f

Please sign in to comment.