diff --git a/frontend/src/extensions/NoteLink.ts b/frontend/src/extensions/NoteLink.ts new file mode 100644 index 0000000..76f73e9 --- /dev/null +++ b/frontend/src/extensions/NoteLink.ts @@ -0,0 +1,74 @@ +import { Mark, markInputRule } from "@tiptap/core"; + +export const NoteLink = Mark.create({ + name: "noteLink", + inclusive: false, + addAttributes() { + return { + noteId: { + default: null, + parseHTML: (element) => { + return element.getAttribute("data-note-id"); + }, + renderHTML: (attributes) => { + return { + "data-note-id": attributes.noteId, + }; + }, + }, + title: { + default: "", + parseHTML: (element) => { + return element.getAttribute("data-title"); + }, + renderHTML: (attributes) => { + return { + "data-title": attributes.title, + }; + }, + }, + }; + }, + parseHTML() { + return [ + { + tag: "span[data-note-id]", + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ["span", { ...HTMLAttributes, class: "note-link" }, 0]; + }, + + addInputRules() { + return [ + markInputRule({ + find: /\[\[([^\]]+)\]\]$/, + type: this.type, + getAttributes: (match) => { + const title = match[1]; + // TODO: Look up noteId from title + return { + title, + noteId: null, // For now + }; + }, + }), + ]; + }, + + addStorage() { + return { + markdown: { + serialize: (state, mark, parent, index) => { + // Get the text content + const textContent = parent.child(index).text || mark.attrs.title; + + // Return the complete link, no wrapping + return `[[${mark.attrs.title}:${mark.attrs.noteId}]]`; + }, + }, + }; + }, +}); diff --git a/frontend/src/pages/TipTap.tsx b/frontend/src/pages/TipTap.tsx index 5125f7a..c2af615 100644 --- a/frontend/src/pages/TipTap.tsx +++ b/frontend/src/pages/TipTap.tsx @@ -22,6 +22,7 @@ import SquareCheckIcon from "../assets/fontawesome/svg/square-check.svg?react"; import CodeBracketIcon from "../assets/fontawesome/svg/code-simple.svg?react"; // @ts-ignore import QuoteLeftIcon from "../assets/fontawesome/svg/quote-left.svg?react"; +import { NoteLink } from "@/extensions/NoteLink"; interface TiptapEditorProps { content: string; @@ -37,6 +38,7 @@ export const TiptapEditor = ({ const editor = useEditor({ extensions: [ ListKit, + NoteLink, StarterKit.configure({ heading: { levels: [1, 2, 3, 4, 5, 6], @@ -189,6 +191,19 @@ export const TiptapEditor = ({ */} {/* Editor content */} + => { + const flatenedNotes = flattenNotes(folderTree); + const noteMap = new Map(); + + for (const note of flatenedNotes) { + noteMap.set(note.id, note); + } + + return noteMap; +}; + +export const flattenNotes = (response: FolderTreeResponse): NoteRead[] => { + const allNotes = [...response.orphanedNotes]; + + const processFolder = (folder: FolderTreeNode) => { + allNotes.push(...folder.notes); + folder.children.forEach(processFolder); + }; + + response.folders.forEach(processFolder); + + return allNotes; +};