feat(notes): [[note]] is an inline link, not bracketed text
Choosing a note from the [[ completion wrote [[Title]] as plain text. It made a real link behind the scenes — it showed in the panel under the note — but in the note itself it stayed brackets, and "it doesn't become a link" is exactly how that reads. A wiki link is a link where it is written. Now a [[note]] renders inline as the title, in the accent colour, and a click on it opens the note it points at. It is atomic — contenteditable="false" — so it moves and deletes as one thing and a click is a click, not a caret landing mid-word. And it appears the moment it is chosen: the completion drops the finished link in, brackets and all swallowed, rather than text waiting to become a link when the note is next opened. The seam holds both ways: markdownToHtml lifts [[title]] into the link the same way it already lifts an image, and htmlToMarkdown reads the link back to [[title]] by the title it carries — so what is on disk is still the same Markdown, and a note opened, saved and reopened is unchanged. e2e: choosing a note writes an inline link, it survives the round-trip through Markdown and a reload, and clicking it opens the note it names. Full suite green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b07938e351
commit
8022502015
+33
-6
@@ -70,19 +70,39 @@ const attr = (s) => escapeHtml(s).replace(/"/g, '"').replace(/'/g, ''')
|
||||
* det overlever hverken en contenteditable eller en Markdown-fil.
|
||||
*/
|
||||
const IMAGE = /!\[([^\]]*)\]\((\/api\/v1\/attachments\/[0-9a-f-]+)\)/g;
|
||||
// [[a note by title]], the spelling every note app settled on. Non-greedy, so two
|
||||
// links on one line stay two.
|
||||
const NOTELINK = /\[\[([^\]\n]+?)\]\]/g;
|
||||
const MARK = "\u0000";
|
||||
|
||||
/** Inline marks, innermost first so a bold word inside italics survives both. */
|
||||
function inlineToHtml(text) {
|
||||
// Lifted out before anything escapes or marks the text, and dropped back in
|
||||
// whole at the end — the same placeholder trick the image already used, now for
|
||||
// two kinds of thing the surrounding markup must not touch.
|
||||
const pieces = [];
|
||||
const lift = (html) => {
|
||||
pieces.push(html);
|
||||
return `${MARK}${pieces.length - 1}${MARK}`;
|
||||
};
|
||||
|
||||
// Kun vores egen adresse bliver til et billede. Et `` fra en
|
||||
// indsat tekst ville ellers hente fra en fremmed vært, når noten åbnes, og
|
||||
// fortælle den, at den er blevet læst — og en note er det sidste sted, man vil
|
||||
// have en sporingspixel. Alt andet står som den tekst, det er.
|
||||
const images = [];
|
||||
const lifted = (text ?? '').replace(IMAGE, (_, alt, src) => {
|
||||
images.push(`<img src="${src}" alt="${attr(alt)}">`);
|
||||
return `${MARK}${images.length - 1}${MARK}`;
|
||||
});
|
||||
let lifted = (text ?? '').replace(IMAGE, (_, alt, src) => lift(`<img src="${src}" alt="${attr(alt)}">`));
|
||||
|
||||
// A [[note]] is a link you can follow, not the brackets it was typed as. Made an
|
||||
// atomic token — contenteditable="false" — so it moves and deletes as one thing,
|
||||
// and it carries the title it points at in data-note; the editor reads that on a
|
||||
// click. htmlToMarkdown turns it back into [[title]] exactly.
|
||||
lifted = lifted.replace(
|
||||
NOTELINK,
|
||||
(_, title) =>
|
||||
lift(
|
||||
`<a class="notelink" data-note="${attr(title)}" contenteditable="false">${escapeHtml(title)}</a>`
|
||||
)
|
||||
);
|
||||
|
||||
const marked = escapeHtml(lifted)
|
||||
.replace(/<u>(.+?)<\/u>/g, '<u>$1</u>')
|
||||
@@ -91,7 +111,7 @@ function inlineToHtml(text) {
|
||||
.replace(/~~(.+?)~~/g, '<s>$1</s>')
|
||||
.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
|
||||
|
||||
return marked.replace(new RegExp(MARK + '(\\d+)' + MARK, 'g'), (_, i) => images[Number(i)] ?? '');
|
||||
return marked.replace(new RegExp(MARK + '(\\d+)' + MARK, 'g'), (_, i) => pieces[Number(i)] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,6 +254,13 @@ function inlineToMarkdown(node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return '';
|
||||
|
||||
// A note link goes back to the brackets it came from, by the title it carries
|
||||
// rather than by the text it shows — the two are the same today, but the title
|
||||
// is the thing that must survive the round trip.
|
||||
if (node.tagName === 'A' && node.classList?.contains('notelink')) {
|
||||
return `[[${node.getAttribute('data-note') ?? node.textContent}]]`;
|
||||
}
|
||||
|
||||
const inner = [...node.childNodes].map(inlineToMarkdown).join('');
|
||||
switch (node.tagName) {
|
||||
// Et billede har ingen tekst i sig, så uden det her læses det som ingenting
|
||||
|
||||
Reference in New Issue
Block a user