mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +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
+26
-2
@@ -2,7 +2,7 @@ import { tick } from 'svelte'
|
||||
import { api, ApiError, isContainer, type BackupLogin, type BackupStatus } from './api'
|
||||
import { workspace } from './store.svelte'
|
||||
import { emptyTrash } from './Trash.svelte'
|
||||
import { formatDay, parseDay, parseDue, relative, sealedNote, type Heading, type ScrollAmount, type SidewaysAmount } from './util'
|
||||
import { formatDay, parseDay, parseDue, relative, sealedNote, titleHref, type Heading, type ScrollAmount, type SidewaysAmount } from './util'
|
||||
|
||||
export type { ScrollAmount, SidewaysAmount }
|
||||
|
||||
@@ -76,13 +76,24 @@ const requireOpen = () => {
|
||||
return workspace.open
|
||||
}
|
||||
|
||||
const TITLE_HREF = titleHref('')
|
||||
|
||||
/**
|
||||
* Where a link in a document goes. `/n/` links are the workspace's own — the
|
||||
* importer writes them between documents — and are opened in place; anything
|
||||
* importer writes them between documents — and are opened in place, as is a
|
||||
* `[[title]]`, or the offer to write it when no document has it; anything
|
||||
* else would take the workspace's tab with it, so it gets a tab of its own,
|
||||
* as does a workspace link asked for with `gx`.
|
||||
*/
|
||||
export function followLink(href: string, tab = false) {
|
||||
if (href.startsWith(TITLE_HREF)) {
|
||||
const title = decodeURIComponent(href.slice(TITLE_HREF.length))
|
||||
const path = workspace.open?.wikilinks[title]
|
||||
if (tab) {
|
||||
if (!path) return workspace.say(`no document called "${title}" yet`, 'error')
|
||||
href = `/n/${path}`
|
||||
} else return void workspace.guard(() => workspace.followTitle(title))
|
||||
}
|
||||
if (href.startsWith('/n/') && !tab) {
|
||||
const path = href.slice(3).split('#')[0]
|
||||
return void workspace.guard(() => workspace.openPath(decodeURIComponent(path)))
|
||||
@@ -789,6 +800,19 @@ export const commands: CommandSpec[] = [
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backlinks',
|
||||
aliases: ['bl'],
|
||||
group: 'documents',
|
||||
help: 'the documents that link to the open one — j/k, Enter opens one (gb)',
|
||||
run: async () => {
|
||||
if (!requireOpen()) return
|
||||
await workspace.guard(async () => {
|
||||
await workspace.loadBacklinks()
|
||||
workspace.overlay = 'backlinks'
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'trash',
|
||||
args: '[purge]',
|
||||
|
||||
Reference in new issue
Block a user