Skip to content

Commit 86d40d1

Browse files
authored
Merge pull request #126 from boostcampwm-2024/bug-fe-#125
여러 에디터에서 동시 편집이 안되는 문제 해결
2 parents 48822e0 + dbb6ac7 commit 86d40d1

File tree

7 files changed

+132
-135
lines changed

7 files changed

+132
-135
lines changed

backend/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function bootstrap() {
88
const app = await NestFactory.create(AppModule);
99
app.useWebSocketAdapter(new WsAdapter(app));
1010
app.useGlobalFilters(new HttpExceptionFilter());
11-
11+
1212
const config = new DocumentBuilder()
1313
.setTitle('OctoDocs')
1414
.setDescription('OctoDocs API 명세서')

frontend/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
22

33
import Sidebar from "./components/sidebar";
44
import HoverTrigger from "./components/HoverTrigger";
5-
import EditorView from "./components/editor/EditorView";
5+
import EditorView from "./components/EditorView";
66
import SideWrapper from "./components/layout/SideWrapper";
77
import Canvas from "./components/canvas";
88

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,126 @@
11
import usePageStore from "@/store/usePageStore";
22
import Editor from "./editor";
3-
import { usePage } from "@/hooks/usePages";
3+
import {
4+
usePage,
5+
useUpdatePage,
6+
useOptimisticUpdatePage,
7+
} from "@/hooks/usePages";
8+
import EditorLayout from "./layout/EditorLayout";
9+
import EditorTitle from "./editor/EditorTitle";
10+
import { EditorInstance } from "novel";
11+
import { useEffect, useRef, useState } from "react";
12+
import { useDebouncedCallback } from "use-debounce";
13+
import { WebsocketProvider } from "y-websocket";
14+
import * as Y from "yjs";
15+
import SaveStatus from "./editor/ui/SaveStatus";
416

517
export default function EditorView() {
618
const { currentPage } = usePageStore();
7-
const { page } = usePage(currentPage);
19+
const { page, isLoading } = usePage(currentPage);
20+
const [editorKey, setEditorKey] = useState(0);
21+
const [saveStatus, setSaveStatus] = useState<"saved" | "unsaved">("saved");
822

9-
if (!page) {
10-
return <div></div>;
23+
const ydoc = useRef<Y.Doc>();
24+
const provider = useRef<WebsocketProvider>();
25+
26+
useEffect(() => {
27+
if (currentPage === null) return;
28+
29+
if (provider.current) {
30+
provider.current.disconnect();
31+
provider.current.destroy();
32+
}
33+
if (ydoc.current) {
34+
ydoc.current.destroy();
35+
}
36+
37+
const doc = new Y.Doc();
38+
const wsProvider = new WebsocketProvider(
39+
"ws://localhost:1234",
40+
`document-${currentPage}`,
41+
doc,
42+
);
43+
44+
ydoc.current = doc;
45+
provider.current = wsProvider;
46+
47+
setEditorKey((prev) => prev + 1);
48+
49+
return () => {
50+
wsProvider.disconnect();
51+
wsProvider.destroy();
52+
doc.destroy();
53+
};
54+
}, [currentPage]);
55+
56+
const pageTitle = page?.title ?? "제목없음";
57+
const pageContent = page?.content ?? {};
58+
59+
const updatePageMutation = useUpdatePage();
60+
const optimisticUpdatePageMutation = useOptimisticUpdatePage({
61+
id: currentPage ?? 0,
62+
});
63+
64+
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
65+
if (currentPage === null) return;
66+
67+
setSaveStatus("unsaved");
68+
69+
optimisticUpdatePageMutation.mutate(
70+
{
71+
pageData: { title: e.target.value, content: pageContent },
72+
},
73+
{
74+
onSuccess: () => setSaveStatus("saved"),
75+
onError: () => setSaveStatus("unsaved"),
76+
},
77+
);
78+
};
79+
80+
const handleEditorUpdate = useDebouncedCallback(
81+
async ({ editor }: { editor: EditorInstance }) => {
82+
if (currentPage === null) {
83+
return;
84+
}
85+
86+
const json = editor.getJSON();
87+
88+
setSaveStatus("unsaved");
89+
updatePageMutation.mutate(
90+
{ id: currentPage, pageData: { title: pageTitle, content: json } },
91+
{
92+
onSuccess: () => setSaveStatus("saved"),
93+
onError: () => setSaveStatus("unsaved"),
94+
},
95+
);
96+
},
97+
500,
98+
);
99+
100+
if (isLoading || !page || currentPage === null) {
101+
return (
102+
<div>
103+
{isLoading && <div>"isLoading"</div>}
104+
{!page && <div>"!page"</div>}
105+
{currentPage === null && <div>"currrentPage === null"</div>}
106+
</div>
107+
);
11108
}
12109

110+
if (!ydoc.current || !provider.current) return <div>로딩중</div>;
111+
13112
return (
14-
<Editor key={page.id} initialContent={page.content} pageId={page.id} />
113+
<EditorLayout>
114+
<SaveStatus saveStatus={saveStatus} />
115+
<EditorTitle title={pageTitle} onTitleChange={handleTitleChange} />
116+
<Editor
117+
key={editorKey}
118+
initialContent={pageContent}
119+
pageId={currentPage}
120+
ydoc={ydoc.current}
121+
provider={provider.current}
122+
onEditorUpdate={handleEditorUpdate}
123+
/>
124+
</EditorLayout>
15125
);
16126
}

frontend/src/components/editor/EditorTitle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export default function EditorTitle({
77
title,
88
onTitleChange,
99
}: EditorTitleProps) {
10+
console.log(title);
1011
return (
1112
<div className="p-12 pb-0">
1213
<input

frontend/src/components/editor/EditorView.tsx

Lines changed: 0 additions & 108 deletions
This file was deleted.

frontend/src/components/editor/index.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import { useState } from "react";
22
import {
33
EditorRoot,
44
EditorCommand,
@@ -39,12 +39,7 @@ interface EditorProp {
3939
provider: WebsocketProvider;
4040
}
4141

42-
const Editor = ({
43-
initialContent,
44-
onEditorUpdate,
45-
ydoc,
46-
provider,
47-
}: EditorProp) => {
42+
const Editor = ({ onEditorUpdate, ydoc, provider }: EditorProp) => {
4843
const [openNode, setOpenNode] = useState(false);
4944
const [openColor, setOpenColor] = useState(false);
5045
const [openLink, setOpenLink] = useState(false);
@@ -57,19 +52,17 @@ const Editor = ({
5752
onContentError={({ disableCollaboration }) => {
5853
disableCollaboration();
5954
}}
60-
onCreate={({ editor: currentEditor }) => {
61-
provider.on("synced", () => {
62-
console.log(ydoc);
63-
64-
if (
65-
!ydoc.getMap("config").get("initialContentLoaded") &&
66-
currentEditor
67-
) {
68-
ydoc.getMap("config").set("initialContentLoaded", true);
69-
currentEditor.commands.setContent(initialContent as JSONContent);
70-
}
71-
});
72-
}}
55+
// onCreate={({ editor: currentEditor }) => {
56+
// provider.on("synced", () => {
57+
// if (
58+
// !ydoc.getMap("config").get("initialContentLoaded") &&
59+
// currentEditor
60+
// ) {
61+
// ydoc.getMap("config").set("initialContentLoaded", true);
62+
// ydoc.getMap("content").set("content", initialContent);
63+
// }
64+
// });
65+
// }}
7366
extensions={[
7467
...extensions,
7568
Collaboration.extend().configure({

frontend/src/hooks/useNoteList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const useNoteList = () => {
1313
const deleteMutation = useDeletePage();
1414

1515
const handleNoteClick = (id: number) => {
16+
console.log("handleNoteClick", id);
1617
setCurrentPage(id);
1718
};
1819

0 commit comments

Comments
 (0)