fix(notes): a [[note]] link is one you can follow
Picking a note from the [[ completion wrote [[Title]] and the server made a real link from it — but the link only ever showed in the panel under the note as plain text. It said the note pointed somewhere and gave no way to go there, which reads as "it didn't become a link at all". The links in that panel are buttons now. A [[note]] resolves by its title to the note — the one already on the list, or looked up if not — and opens it in place; a #project goes to its page; a task link to the task. Titles with punctuation work, brackets and parentheses included, because the match is on the whole title, not a parse of it. e2e: after linking a note, clicking its chip opens the note it names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2d7b6a1d5e
commit
de95df44c1
@@ -2651,6 +2651,10 @@ test('[[ foreslår en note, og linket lander i noten', async ({ page }) => {
|
||||
await ed.blur();
|
||||
await expect(page.locator('.links')).toContainText('Rejseplan til Berlin');
|
||||
|
||||
// And it is a link you can follow: clicking the chip opens the note it names.
|
||||
await page.locator('.links .link', { hasText: 'Rejseplan til Berlin' }).click();
|
||||
await expect(ed.locator('h1')).toHaveText('Rejseplan til Berlin');
|
||||
|
||||
expect(trouble).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export const da = {
|
||||
'notes.unshared': 'Noten er din igen.',
|
||||
// Deling med enkeltpersoner, ved siden af projektet.
|
||||
'notes.groupSharedWithMe': 'Delt med mig',
|
||||
'notes.linkOpen': 'Åbn',
|
||||
'notes.linkMissing': 'Noten blev ikke fundet.',
|
||||
'notes.sharedBy': 'Delt af {name}',
|
||||
'notes.sharePeople': 'Del med personer',
|
||||
'notes.sharePick': 'Vælg en person …',
|
||||
|
||||
@@ -18,6 +18,8 @@ export const en = {
|
||||
'notes.unshared': 'The note is yours again.',
|
||||
// Sharing with individual people, alongside the project.
|
||||
'notes.groupSharedWithMe': 'Shared with me',
|
||||
'notes.linkOpen': 'Open',
|
||||
'notes.linkMissing': 'That note could not be found.',
|
||||
'notes.sharedBy': 'Shared by {name}',
|
||||
'notes.sharePeople': 'Share with people',
|
||||
'notes.sharePick': 'Choose someone …',
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { api, humanMessage } from '$lib/api.js';
|
||||
import { app } from '$lib/stores.svelte.js';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { t, tag } from '$lib/i18n.svelte.js';
|
||||
import { NOTE, startDrag } from '$lib/dnd.js';
|
||||
import { colorVar } from '$lib/colors.js';
|
||||
@@ -653,6 +654,44 @@
|
||||
// What the note points at, for the panel under the editor.
|
||||
let links = $derived(selected?.links ?? []);
|
||||
|
||||
/**
|
||||
* Follows a link from the panel to the thing it names.
|
||||
*
|
||||
* A [[note]] resolves by its title to a note — the one on the list if it is
|
||||
* there, otherwise looked up — and opens it in place. A #project and a task go
|
||||
* to their own pages. Until now these were text and nothing else: the note said
|
||||
* it pointed somewhere and there was no way to go.
|
||||
*/
|
||||
async function openLink(link) {
|
||||
if (link.kind === 'note') {
|
||||
const wanted = link.target_id.toLowerCase();
|
||||
const here = notes.find((n) => (n.title ?? '').toLowerCase() === wanted);
|
||||
if (here) {
|
||||
open(here);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const found = (await api.notes({ q: link.target_id })).notes ?? [];
|
||||
const hit = found.find((n) => (n.title ?? '').toLowerCase() === wanted) ?? found[0];
|
||||
if (hit) open(hit);
|
||||
else app.toast(t('notes.linkMissing'));
|
||||
} catch (e) {
|
||||
app.toast(humanMessage(e));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (link.kind === 'project') {
|
||||
const project = app.projects.find(
|
||||
(p) => p.name.toLowerCase() === link.target_id.toLowerCase()
|
||||
);
|
||||
if (project) goto(`/projekt/${project.id}`);
|
||||
return;
|
||||
}
|
||||
if (link.kind === 'task') {
|
||||
goto(`/opgave/${link.target_id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -889,7 +928,9 @@
|
||||
{#if links.length}
|
||||
<span class="links">
|
||||
{#each links as link}
|
||||
<span class="link">{label(link)}</span>
|
||||
<button class="link" onclick={() => openLink(link)} title={t('notes.linkOpen')}>
|
||||
{label(link)}
|
||||
</button>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -1615,9 +1656,17 @@
|
||||
.link {
|
||||
color: var(--ink-muted);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0 var(--s1);
|
||||
font-family: var(--mono, ui-monospace, monospace);
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.link:hover {
|
||||
color: var(--ink);
|
||||
background: var(--surface-raised);
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
/* Sharing lives in a popover above the Del button, so the footer stays a status
|
||||
|
||||
Reference in New Issue
Block a user