Skip to content

Commit 628bff3

Browse files
committed
feat: tiptap markdown to json conversion
1 parent 2dbd6d1 commit 628bff3

5 files changed

Lines changed: 71 additions & 42 deletions

File tree

backend/internal/api/handler/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (h *Handler) ImportNotesHandler(w http.ResponseWriter, r *http.Request) {
9797
http.Error(w, "unauthorized", http.StatusUnauthorized)
9898
return
9999
}
100-
fmt.Println(clerkID)
100+
fmt.Println(clerkID,"yay")
101101
//response.WriteResponse(w, http.StatusAccepted, fmt.Sprintf("%d", noteID))
102102
}
103103

backend/internal/api/validator/validator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func ImportNotesValidator(r *http.Request) *httpError {
142142
}
143143

144144
contentType := r.Header.Get("Content-Type")
145-
if !strings.HasPrefix(contentType, "multipart/form-data") {
145+
if contentType != "application/json" {
146146
log.Printf("invalid content-type: %s", contentType)
147147
return &httpError{Status: http.StatusUnsupportedMediaType, Msg: "media not supported"}
148148
}

frontend/src/components/Editor.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
1-
import { useEditor, EditorContent } from "@tiptap/react";
2-
import StarterKit from "@tiptap/starter-kit";
3-
import { Markdown } from "@tiptap/markdown";
4-
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
5-
import { createLowlight, common } from "lowlight";
6-
import { useEffect } from "react";
1+
import { EditorContent, useEditor } from "@tiptap/react";
2+
import { useEffect, useRef } from "react";
73
import type { Note } from "../hooks/useNotes";
8-
9-
const lowlight = createLowlight(common);
4+
import { editorExtensions, normalizeEditorContent } from "../editor";
105

116
type Props = {
127
activeNote: Note | null;
138
updateNoteContent: (json: object) => void;
149
};
1510

1611
export function Editor({ activeNote, updateNoteContent }: Props) {
12+
const updateNoteContentRef = useRef(updateNoteContent);
13+
14+
useEffect(() => {
15+
updateNoteContentRef.current = updateNoteContent;
16+
}, [updateNoteContent]);
17+
1718
const editor = useEditor({
18-
extensions: [
19-
StarterKit.configure({ codeBlock: false }),
20-
CodeBlockLowlight.configure({ lowlight }),
21-
Markdown,
22-
],
23-
content: activeNote?.content ?? "",
24-
contentType: "markdown",
19+
extensions: editorExtensions,
20+
content: normalizeEditorContent(activeNote?.content),
2521
onUpdate({ editor }) {
26-
updateNoteContent(editor.getJSON());
22+
updateNoteContentRef.current(editor.getJSON());
2723
},
2824
onBlur({ editor }) {
2925
setTimeout(() => {
@@ -32,7 +28,6 @@ export function Editor({ activeNote, updateNoteContent }: Props) {
3228
},
3329
});
3430

35-
// Auto-focus on mount and when the window regains focus
3631
useEffect(() => {
3732
const timer = setTimeout(() => editor?.commands.focus(), 100);
3833
return () => clearTimeout(timer);
@@ -44,16 +39,17 @@ export function Editor({ activeNote, updateNoteContent }: Props) {
4439
return () => window.removeEventListener("focus", onFocus);
4540
}, [editor]);
4641

47-
// When switching notes, update editor content
4842
useEffect(() => {
49-
if (!editor || !activeNote) return;
43+
if (!editor) return;
44+
5045
const currentJson = JSON.stringify(editor.getJSON());
51-
const targetContent = activeNote.content;
46+
const targetContent = normalizeEditorContent(activeNote?.content);
5247
const targetJson = JSON.stringify(targetContent);
48+
5349
if (currentJson !== targetJson) {
54-
editor.commands.setContent(targetContent as string | object, { emitUpdate: false });
50+
editor.commands.setContent(targetContent, { emitUpdate: false });
5551
}
56-
}, [editor, activeNote?.id]); // eslint-disable-line react-hooks/exhaustive-deps
52+
}, [editor, activeNote]);
5753

5854
return (
5955
<EditorContent

frontend/src/editor.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Content, JSONContent } from "@tiptap/core";
2+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
3+
import { Markdown, MarkdownManager } from "@tiptap/markdown";
4+
import StarterKit from "@tiptap/starter-kit";
5+
import { createLowlight, common } from "lowlight";
6+
7+
const lowlight = createLowlight(common);
8+
9+
export const editorExtensions = [
10+
StarterKit.configure({ codeBlock: false }),
11+
CodeBlockLowlight.configure({ lowlight }),
12+
Markdown,
13+
];
14+
15+
const markdownManager = new MarkdownManager({
16+
extensions: editorExtensions,
17+
});
18+
19+
export function parseMarkdownContent(markdown: string): JSONContent {
20+
return markdownManager.parse(markdown);
21+
}
22+
23+
export function normalizeEditorContent(
24+
content: object | string | null | undefined
25+
): Content {
26+
if (typeof content === "string") {
27+
return parseMarkdownContent(content);
28+
}
29+
30+
return (content ?? parseMarkdownContent("")) as Content;
31+
}

frontend/src/hooks/useNotes.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useCallback, useEffect } from "react";
22
import { useAuth } from "@clerk/clerk-react";
3+
import { parseMarkdownContent } from "../editor";
34

45
const BASEURL = import.meta.env.VITE_API_BASE_URL;
56

@@ -106,25 +107,26 @@ async function deleteNoteRequest(id: string, getToken: GetToken): Promise<void>
106107

107108
async function importNotes(file: File, getToken: GetToken): Promise<void> {
108109
const url = BASEURL + "/notes/imports";
109-
const formData = new FormData();
110-
formData.append("file", file);
111-
112110
const headers = await authHeaders(getToken);
113-
114-
try {
115-
const response = await fetch(url, {
116-
method: "POST",
117-
headers,
118-
credentials: "include",
119-
body: formData,
120-
});
121-
122-
if (!response.ok) {
123-
throw new Error("request failed");
124-
}
125-
} catch (err) {
126-
console.error(err);
127-
throw err;
111+
const markdown = await file.text();
112+
const content = parseMarkdownContent(markdown);
113+
const title = file.name.replace(/\.md$/i, "");
114+
115+
const response = await fetch(url, {
116+
method: "POST",
117+
headers: {
118+
...headers,
119+
"Content-Type": "application/json",
120+
},
121+
credentials: "include",
122+
body: JSON.stringify({
123+
title,
124+
content,
125+
}),
126+
});
127+
128+
if (!response.ok) {
129+
throw new Error("request failed");
128130
}
129131
}
130132

0 commit comments

Comments
 (0)