mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 17:07:00 +01:00
links: a rename or a move no longer breaks them
A document links to another by its path, `[the plan](/n/work/plan)`, or now by its title, `[[Plan]]` or `[[Plan|the plan]]`. What each document links to is kept in a new `links` table, rebuilt whenever its text is written: from the browser, over the mount, or by an import. A title is looked for in the linking document's space first, then in every other. Renaming or moving a document, or a folder above it, rewrites the links to it in every document that has one, in the same transaction: the path to the new one, a title to the new name, with the label left as it was. Each rewritten document keeps a revision of what it said before. A link to something in the trash keeps its row, so it follows the thing when it is restored somewhere else. A document created, or renamed, under a title that a link was waiting for is found by that link from then on. The reader draws `[[titles]]` as links, and a title no document has yet as a dimmed one that offers to write that document. `gf` follows either from the editor, and `[[` there completes titles from the search. `gb` and `:backlinks` list the documents that link to the open one, with the line each does it on; the line under the title counts them. A share page shows these links as plain text, as it does every /n/ link. The documents written before this are indexed once, when the server starts and finds the table empty.
This commit is contained in:
1 parent
fc2be66b0c
commit
a42f9074a2
23 files changed
+1281
-35
No files matched your search
+39
-3
@@ -1,6 +1,7 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
import { marked, type TokenizerAndRendererExtension } from 'marked'
|
||||
import { mathEngine } from './math.svelte'
|
||||
import { titleHref } from './util'
|
||||
|
||||
marked.setOptions({ gfm: true, breaks: true })
|
||||
|
||||
@@ -37,8 +38,38 @@ const mathInline: TokenizerAndRendererExtension = {
|
||||
renderer: (token) => tex(token.text, token.display),
|
||||
}
|
||||
|
||||
/*
|
||||
* `[[Plan]]` and `[[Plan|the plan]]`: a link to a document by its title. The
|
||||
* server says where each title leads, since it is the one that knows the
|
||||
* titles; one that leads nowhere is drawn dimmed, and following it offers to
|
||||
* write the document.
|
||||
*/
|
||||
const WIKILINK = /^\[\[([^[\]|\n]+)(?:\|([^[\]\n]*))?\]\]/
|
||||
|
||||
/** What the page being rendered says about its `[[titles]]`, for the renderer. */
|
||||
let wikiContext: { leads: Record<string, string>; sharing: boolean } = { leads: {}, sharing: false }
|
||||
|
||||
const wikilink: TokenizerAndRendererExtension = {
|
||||
name: 'wikilink',
|
||||
level: 'inline',
|
||||
start: (src) => src.indexOf('[['),
|
||||
tokenizer(src) {
|
||||
const match = WIKILINK.exec(src)
|
||||
const target = match?.[1].trim()
|
||||
if (match && target) return { type: 'wikilink', raw: match[0], target, label: match[2]?.trim() || target }
|
||||
},
|
||||
renderer: (token) => {
|
||||
const label = escapeHtml(token.label)
|
||||
// The page it leads to is behind the login, as any /n/ link on a share is.
|
||||
if (wikiContext.sharing) return `<span class="private-link" title="a page of the private workspace">${label}</span>`
|
||||
const path = wikiContext.leads[token.target]
|
||||
if (path) return `<a href="/n/${encodeURI(path)}" class="wikilink">${label}</a>`
|
||||
return `<a href="${titleHref(token.target)}" class="wikilink missing" title="not written yet — follow it to write it">${label}</a>`
|
||||
},
|
||||
}
|
||||
|
||||
marked.use({
|
||||
extensions: [mathBlock, mathInline],
|
||||
extensions: [mathBlock, mathInline, wikilink],
|
||||
renderer: {
|
||||
code(token) {
|
||||
return token.lang === 'math' ? tex(token.text, true) : false
|
||||
@@ -84,13 +115,18 @@ function escapeHtml(text: string): string {
|
||||
* the cookie reading the document with its key handed out, so the key itself
|
||||
* never sits in a URL a proxy could log. A link to another document, an
|
||||
* address inside the private workspace that would lead the reader to its
|
||||
* login page, is kept as its text.
|
||||
* login page, is kept as its text. `wikilinks` says where the `[[titles]]`
|
||||
* lead, as the server answered for the document.
|
||||
*/
|
||||
export function renderMarkdown(source: string, share?: { token: string }): string {
|
||||
export function renderMarkdown(
|
||||
source: string,
|
||||
{ share, wikilinks = {} }: { share?: { token: string }; wikilinks?: Record<string, string> } = {},
|
||||
): string {
|
||||
const md = share
|
||||
? source.replaceAll('/api/media/', `/api/share/${encodeURIComponent(share.token)}/media/`)
|
||||
: source
|
||||
|
||||
wikiContext = { leads: wikilinks, sharing: !!share }
|
||||
const html = marked.parse(md, { async: false }) as string
|
||||
if (!share) return DOMPurify.sanitize(html, SANITIZE)
|
||||
|
||||
|
||||
Reference in new issue
Block a user