Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

여러 에디터에서 동시 편집이 안되는 문제 해결 #126

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));
app.useGlobalFilters(new HttpExceptionFilter());

const config = new DocumentBuilder()
.setTitle('OctoDocs')
.setDescription('OctoDocs API 명세서')
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import Sidebar from "./components/sidebar";
import HoverTrigger from "./components/HoverTrigger";
import EditorView from "./components/editor/EditorView";
import EditorView from "./components/EditorView";
import SideWrapper from "./components/layout/SideWrapper";
import Canvas from "./components/canvas";

Expand Down
120 changes: 115 additions & 5 deletions frontend/src/components/EditorView.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,126 @@
import usePageStore from "@/store/usePageStore";
import Editor from "./editor";
import { usePage } from "@/hooks/usePages";
import {
usePage,
useUpdatePage,
useOptimisticUpdatePage,
} from "@/hooks/usePages";
import EditorLayout from "./layout/EditorLayout";
import EditorTitle from "./editor/EditorTitle";
import { EditorInstance } from "novel";
import { useEffect, useRef, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";
import SaveStatus from "./editor/ui/SaveStatus";

export default function EditorView() {
const { currentPage } = usePageStore();
const { page } = usePage(currentPage);
const { page, isLoading } = usePage(currentPage);
const [editorKey, setEditorKey] = useState(0);
const [saveStatus, setSaveStatus] = useState<"saved" | "unsaved">("saved");

if (!page) {
return <div></div>;
const ydoc = useRef<Y.Doc>();
const provider = useRef<WebsocketProvider>();

useEffect(() => {
if (currentPage === null) return;

if (provider.current) {
provider.current.disconnect();
provider.current.destroy();
}
if (ydoc.current) {
ydoc.current.destroy();
}

const doc = new Y.Doc();
const wsProvider = new WebsocketProvider(
"ws://localhost:1234",
`document-${currentPage}`,
doc,
);

ydoc.current = doc;
provider.current = wsProvider;

setEditorKey((prev) => prev + 1);

return () => {
wsProvider.disconnect();
wsProvider.destroy();
doc.destroy();
};
}, [currentPage]);

const pageTitle = page?.title ?? "제목없음";
const pageContent = page?.content ?? {};

const updatePageMutation = useUpdatePage();
const optimisticUpdatePageMutation = useOptimisticUpdatePage({
id: currentPage ?? 0,
});

const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (currentPage === null) return;

setSaveStatus("unsaved");

optimisticUpdatePageMutation.mutate(
{
pageData: { title: e.target.value, content: pageContent },
},
{
onSuccess: () => setSaveStatus("saved"),
onError: () => setSaveStatus("unsaved"),
},
);
};

const handleEditorUpdate = useDebouncedCallback(
async ({ editor }: { editor: EditorInstance }) => {
if (currentPage === null) {
return;
}

const json = editor.getJSON();

setSaveStatus("unsaved");
updatePageMutation.mutate(
{ id: currentPage, pageData: { title: pageTitle, content: json } },
{
onSuccess: () => setSaveStatus("saved"),
onError: () => setSaveStatus("unsaved"),
},
);
},
500,
);

if (isLoading || !page || currentPage === null) {
return (
<div>
{isLoading && <div>"isLoading"</div>}
{!page && <div>"!page"</div>}
{currentPage === null && <div>"currrentPage === null"</div>}
</div>
);
}

if (!ydoc.current || !provider.current) return <div>로딩중</div>;

return (
<Editor key={page.id} initialContent={page.content} pageId={page.id} />
<EditorLayout>
<SaveStatus saveStatus={saveStatus} />
<EditorTitle title={pageTitle} onTitleChange={handleTitleChange} />
<Editor
key={editorKey}
initialContent={pageContent}
pageId={currentPage}
ydoc={ydoc.current}
provider={provider.current}
onEditorUpdate={handleEditorUpdate}
/>
</EditorLayout>
);
}
1 change: 1 addition & 0 deletions frontend/src/components/editor/EditorTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default function EditorTitle({
title,
onTitleChange,
}: EditorTitleProps) {
console.log(title);
return (
<div className="p-12 pb-0">
<input
Expand Down
108 changes: 0 additions & 108 deletions frontend/src/components/editor/EditorView.tsx

This file was deleted.

33 changes: 13 additions & 20 deletions frontend/src/components/editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import {
EditorRoot,
EditorCommand,
Expand Down Expand Up @@ -39,12 +39,7 @@ interface EditorProp {
provider: WebsocketProvider;
}

const Editor = ({
initialContent,
onEditorUpdate,
ydoc,
provider,
}: EditorProp) => {
const Editor = ({ onEditorUpdate, ydoc, provider }: EditorProp) => {
const [openNode, setOpenNode] = useState(false);
const [openColor, setOpenColor] = useState(false);
const [openLink, setOpenLink] = useState(false);
Expand All @@ -57,19 +52,17 @@ const Editor = ({
onContentError={({ disableCollaboration }) => {
disableCollaboration();
}}
onCreate={({ editor: currentEditor }) => {
provider.on("synced", () => {
console.log(ydoc);

if (
!ydoc.getMap("config").get("initialContentLoaded") &&
currentEditor
) {
ydoc.getMap("config").set("initialContentLoaded", true);
currentEditor.commands.setContent(initialContent as JSONContent);
}
});
}}
// onCreate={({ editor: currentEditor }) => {
// provider.on("synced", () => {
// if (
// !ydoc.getMap("config").get("initialContentLoaded") &&
// currentEditor
// ) {
// ydoc.getMap("config").set("initialContentLoaded", true);
// ydoc.getMap("content").set("content", initialContent);
// }
// });
// }}
extensions={[
...extensions,
Collaboration.extend().configure({
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/useNoteList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const useNoteList = () => {
const deleteMutation = useDeletePage();

const handleNoteClick = (id: number) => {
console.log("handleNoteClick", id);
setCurrentPage(id);
};

Expand Down
Loading