Compare commits
1 commit
main
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
| b3787eeb3c |
4 changed files with 123 additions and 0 deletions
74
frontend/src/extensions/NoteLink.ts
Normal file
74
frontend/src/extensions/NoteLink.ts
Normal file
|
|
@ -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}]]`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -22,6 +22,7 @@ import SquareCheckIcon from "../assets/fontawesome/svg/square-check.svg?react";
|
||||||
import CodeBracketIcon from "../assets/fontawesome/svg/code-simple.svg?react";
|
import CodeBracketIcon from "../assets/fontawesome/svg/code-simple.svg?react";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import QuoteLeftIcon from "../assets/fontawesome/svg/quote-left.svg?react";
|
import QuoteLeftIcon from "../assets/fontawesome/svg/quote-left.svg?react";
|
||||||
|
import { NoteLink } from "@/extensions/NoteLink";
|
||||||
|
|
||||||
interface TiptapEditorProps {
|
interface TiptapEditorProps {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -37,6 +38,7 @@ export const TiptapEditor = ({
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
ListKit,
|
ListKit,
|
||||||
|
NoteLink,
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
heading: {
|
heading: {
|
||||||
levels: [1, 2, 3, 4, 5, 6],
|
levels: [1, 2, 3, 4, 5, 6],
|
||||||
|
|
@ -189,6 +191,19 @@ export const TiptapEditor = ({
|
||||||
</div>*/}
|
</div>*/}
|
||||||
|
|
||||||
{/* Editor content */}
|
{/* Editor content */}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
editor
|
||||||
|
?.chain()
|
||||||
|
.focus()
|
||||||
|
.setMark("noteLink", { noteId: 123, title: "Test Note" })
|
||||||
|
.insertContent("Test Note")
|
||||||
|
.run();
|
||||||
|
}}
|
||||||
|
className="bg-ctp-blue text-ctp-base px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Insert Test Link
|
||||||
|
</button>
|
||||||
<EditorContent
|
<EditorContent
|
||||||
editor={editor}
|
editor={editor}
|
||||||
className="editor-content h-min-screen p-4! pt-0!"
|
className="editor-content h-min-screen p-4! pt-0!"
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,9 @@
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-link {
|
||||||
|
@apply text-ctp-pink-500;
|
||||||
|
@apply underline;
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
|
|
||||||
28
frontend/src/utils/notes.ts
Normal file
28
frontend/src/utils/notes.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Folder, FolderTreeNode, FolderTreeResponse } from "@/api/folders";
|
||||||
|
import { NoteRead } from "@/api/notes";
|
||||||
|
|
||||||
|
export const createNoteMap = (
|
||||||
|
folderTree: FolderTreeResponse,
|
||||||
|
): Map<number, NoteRead> => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue