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;
+};