mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 04:36:43 +02:00
chore(server): start processing bookmarks
This commit is contained in:
34
apps/server/src/services/notes.spec.ts
Normal file
34
apps/server/src/services/notes.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { findBookmarks } from "./notes.js";
|
||||
|
||||
describe("findBookmarks", () => {
|
||||
it("extracts bookmark IDs from empty anchor tags", () => {
|
||||
const content = `<p>Hello</p><a id="chapter-1"></a><p>World</p>`;
|
||||
expect(findBookmarks(content)).toEqual(["chapter-1"]);
|
||||
});
|
||||
|
||||
it("extracts multiple bookmarks", () => {
|
||||
const content = `<a id="intro"></a><p>Text</p><a id="conclusion"></a>`;
|
||||
expect(findBookmarks(content)).toEqual(["intro", "conclusion"]);
|
||||
});
|
||||
|
||||
it("returns empty array when no bookmarks exist", () => {
|
||||
const content = `<p>No bookmarks here</p>`;
|
||||
expect(findBookmarks(content)).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores anchor tags with href (regular links, not bookmarks)", () => {
|
||||
const content = `<a href="#root/abc123" id="some-id">link</a>`;
|
||||
expect(findBookmarks(content)).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles bookmarks with various valid ID characters", () => {
|
||||
const content = `<a id="my_bookmark-2.0"></a>`;
|
||||
expect(findBookmarks(content)).toEqual(["my_bookmark-2.0"]);
|
||||
});
|
||||
|
||||
it("does not produce duplicates", () => {
|
||||
const content = `<a id="same"></a><a id="same"></a>`;
|
||||
expect(findBookmarks(content)).toEqual(["same"]);
|
||||
});
|
||||
});
|
||||
@@ -454,6 +454,54 @@ function findImageLinks(content: string, foundLinks: FoundLink[]) {
|
||||
return content.replace(/src="[^"]*\/api\/images\//g, 'src="api/images/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts bookmark IDs from CKEditor bookmark anchors (`<a id="..."></a>` without href).
|
||||
* Bookmarks are stored as labels on the note so they can be looked up without parsing content.
|
||||
*/
|
||||
export function findBookmarks(content: string): string[] {
|
||||
const re = /<a\s+id="([^"]+)"[^>]*><\/a>/g;
|
||||
const bookmarks: string[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = re.exec(content))) {
|
||||
// Skip anchors that also have an href (those are regular links, not bookmarks)
|
||||
if (match[0].includes("href=")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = match[1];
|
||||
if (!bookmarks.includes(id)) {
|
||||
bookmarks.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
function saveBookmarks(note: BNote, content: string) {
|
||||
const foundBookmarks = findBookmarks(content);
|
||||
const existingBookmarks = note.getLabels("internalBookmark");
|
||||
|
||||
for (const bookmarkId of foundBookmarks) {
|
||||
const existing = existingBookmarks.find((l) => l.value === bookmarkId);
|
||||
|
||||
if (!existing) {
|
||||
new BAttribute({
|
||||
noteId: note.noteId,
|
||||
type: "label",
|
||||
name: "internalBookmark",
|
||||
value: bookmarkId
|
||||
}).save();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove bookmarks that are no longer in the content
|
||||
const unusedBookmarks = existingBookmarks.filter((l) => !foundBookmarks.includes(l.value));
|
||||
for (const unused of unusedBookmarks) {
|
||||
unused.markAsDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
function findInternalLinks(content: string, foundLinks: FoundLink[]) {
|
||||
const re = /href="[^"]*#root[a-zA-Z0-9_\/]*\/([a-zA-Z0-9_]+)\/?"/g;
|
||||
let match;
|
||||
@@ -695,6 +743,7 @@ function saveLinks(note: BNote, content: string | Buffer) {
|
||||
content = findImageLinks(content, foundLinks);
|
||||
content = findInternalLinks(content, foundLinks);
|
||||
content = findIncludeNoteLinks(content, foundLinks);
|
||||
saveBookmarks(note, content);
|
||||
|
||||
({ forceFrontendReload, content } = checkImageAttachments(note, content));
|
||||
} else if (note.type === "relationMap" && typeof content === "string") {
|
||||
|
||||
Reference in New Issue
Block a user