@@ -25,6 +25,8 @@ import { ColorSelector } from "./selectors/color-selector";
2525import { useDebouncedCallback } from "use-debounce" ;
2626import { useUpdatePage } from "@/hooks/usePages" ;
2727
28+ import { useUpdatePage } from "@/hooks/usePages" ;
29+
2830const extensions = [ ...defaultExtensions , slashCommand ] ;
2931
3032interface EditorProp {
@@ -43,40 +45,23 @@ const Editor = ({ pageId, initialValue }: EditorProp) => {
4345 const [ openNode , setOpenNode ] = useState ( false ) ;
4446 const [ openColor , setOpenColor ] = useState ( false ) ;
4547 const [ openLink , setOpenLink ] = useState ( false ) ;
48+ const [ saveStatus , setSaveStatus ] = useState ( "Saved" ) ;
4649
47- const highlightCodeblocks = ( content : string ) => {
48- const doc = new DOMParser ( ) . parseFromString ( content , "text/html" ) ;
49- doc . querySelectorAll ( "pre code" ) . forEach ( ( el ) => {
50- // @ts -expect-error - highlightElement is not in the types
51- // https://highlightjs.readthedocs.io/en/latest/api.html?highlight=highlightElement#highlightelement
52- hljs . highlightElement ( el ) ;
53- } ) ;
54- return new XMLSerializer ( ) . serializeToString ( doc ) ;
55- } ;
50+ const updatePageMutation = useUpdatePage ( ) ;
5651
5752 const debouncedUpdates = useDebouncedCallback (
5853 async ( editor : EditorInstance ) => {
5954 if ( pageId === undefined ) return ;
6055
6156 const json = editor . getJSON ( ) ;
62- updateMutation . mutate ( {
57+ const response = await updatePageMutation . mutateAsync ( {
6358 id : pageId ,
64- pageData : {
65- title : "제목 없음" ,
66- content : json ,
67- } ,
59+ pageData : json ,
6860 } ) ;
69- window . localStorage . setItem (
70- "html-content" ,
71- highlightCodeblocks ( editor . getHTML ( ) ) ,
72- ) ;
73- window . localStorage . setItem ( pageId . toString ( ) , JSON . stringify ( json ) ) ;
74- window . localStorage . setItem (
75- "markdown" ,
76- editor . storage . markdown . getMarkdown ( ) ,
77- ) ;
61+ if ( response ) {
62+ setSaveStatus ( "Saved" ) ;
63+ }
7864 } ,
79-
8065 500 ,
8166 ) ;
8267
@@ -86,69 +71,77 @@ const Editor = ({ pageId, initialValue }: EditorProp) => {
8671 } , [ pageId ] ) ;
8772
8873 return (
89- < EditorRoot >
90- < EditorContent
91- initialContent = { initialContent === null ? undefined : initialContent }
92- className = "relative h-[720px] w-[520px] overflow-auto border-muted bg-background bg-white sm:rounded-lg sm:border sm:shadow-lg"
93- extensions = { extensions }
94- editorProps = { {
95- handleDOMEvents : {
96- keydown : ( _view , event ) => handleCommandNavigation ( event ) ,
97- } ,
98- attributes : {
99- class : `prose prose-lg prose-headings:font-title font-default focus:outline-none max-w-full` ,
100- } ,
101- } }
102- slotAfter = { < ImageResizer /> }
103- onUpdate = { ( { editor } ) => {
104- debouncedUpdates ( editor ) ;
105- } }
106- >
107- < EditorCommand className = "z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border border-muted bg-background px-1 py-2 shadow-md transition-all" >
108- < EditorCommandEmpty className = "px-2 text-muted-foreground" >
109- No results
110- </ EditorCommandEmpty >
111- < EditorCommandList >
112- { suggestionItems . map ( ( item ) => (
113- < EditorCommandItem
114- value = { item . title }
115- onCommand = { ( val ) => item . command ?.( val ) }
116- className = "flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:cursor-pointer hover:bg-accent aria-selected:bg-accent"
117- key = { item . title }
118- >
119- < div className = "flex h-10 w-10 items-center justify-center rounded-md border border-muted bg-background" >
120- { item . icon }
121- </ div >
122- < div >
123- < p className = "font-medium" > { item . title } </ p >
124- < p className = "text-xs text-muted-foreground" >
125- { item . description }
126- </ p >
127- </ div >
128- </ EditorCommandItem >
129- ) ) }
130- </ EditorCommandList >
131- </ EditorCommand >
132- < EditorBubble
133- tippyOptions = { {
134- placement : "top" ,
74+ < div className = "relative h-[720px] w-[520px] overflow-auto border-muted bg-background bg-white sm:rounded-lg sm:border sm:shadow-lg" >
75+ < div className = "absolute right-5 top-5 z-10 mb-5 flex gap-2" >
76+ < div className = "rounded-lg bg-accent px-2 py-1 text-sm text-muted-foreground" >
77+ { saveStatus }
78+ </ div >
79+ </ div >
80+ < EditorRoot >
81+ < EditorContent
82+ initialContent = { initialContent === null ? undefined : initialContent }
83+ className = ""
84+ extensions = { extensions }
85+ editorProps = { {
86+ handleDOMEvents : {
87+ keydown : ( _view , event ) => handleCommandNavigation ( event ) ,
88+ } ,
89+ attributes : {
90+ class : `prose prose-lg prose-headings:font-title font-default focus:outline-none max-w-full` ,
91+ } ,
92+ } }
93+ slotAfter = { < ImageResizer /> }
94+ onUpdate = { ( { editor } ) => {
95+ debouncedUpdates ( editor ) ;
96+ setSaveStatus ( "Unsaved" ) ;
13597 } }
136- className = "flex w-fit max-w-[90vw] overflow-hidden rounded-md border border-muted bg-background shadow-xl"
13798 >
138- { " " }
139- < Separator orientation = "vertical" />
140- < NodeSelector open = { openNode } onOpenChange = { setOpenNode } />
141- < Separator orientation = "vertical" />
142- < LinkSelector open = { openLink } onOpenChange = { setOpenLink } />
143- < Separator orientation = "vertical" />
144- < MathSelector />
145- < Separator orientation = "vertical" />
146- < TextButtons />
147- < Separator orientation = "vertical" />
148- < ColorSelector open = { openColor } onOpenChange = { setOpenColor } />
149- </ EditorBubble >
150- </ EditorContent >
151- </ EditorRoot >
99+ < EditorCommand className = "z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border border-muted bg-background px-1 py-2 shadow-md transition-all" >
100+ < EditorCommandEmpty className = "px-2 text-muted-foreground" >
101+ No results
102+ </ EditorCommandEmpty >
103+ < EditorCommandList >
104+ { suggestionItems . map ( ( item ) => (
105+ < EditorCommandItem
106+ value = { item . title }
107+ onCommand = { ( val ) => item . command ?.( val ) }
108+ className = "flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:cursor-pointer hover:bg-accent aria-selected:bg-accent"
109+ key = { item . title }
110+ >
111+ < div className = "flex h-10 w-10 items-center justify-center rounded-md border border-muted bg-background" >
112+ { item . icon }
113+ </ div >
114+ < div >
115+ < p className = "font-medium" > { item . title } </ p >
116+ < p className = "text-xs text-muted-foreground" >
117+ { item . description }
118+ </ p >
119+ </ div >
120+ </ EditorCommandItem >
121+ ) ) }
122+ </ EditorCommandList >
123+ </ EditorCommand >
124+ < EditorBubble
125+ tippyOptions = { {
126+ placement : "top" ,
127+ } }
128+ className = "flex w-fit max-w-[90vw] overflow-hidden rounded-md border border-muted bg-background shadow-xl"
129+ >
130+ { " " }
131+ < Separator orientation = "vertical" />
132+ < NodeSelector open = { openNode } onOpenChange = { setOpenNode } />
133+ < Separator orientation = "vertical" />
134+ < LinkSelector open = { openLink } onOpenChange = { setOpenLink } />
135+ < Separator orientation = "vertical" />
136+ < MathSelector />
137+ < Separator orientation = "vertical" />
138+ < TextButtons />
139+ < Separator orientation = "vertical" />
140+ < ColorSelector open = { openColor } onOpenChange = { setOpenColor } />
141+ </ EditorBubble >
142+ </ EditorContent >
143+ </ EditorRoot >
144+ </ div >
152145 ) ;
153146} ;
154147
0 commit comments