[]
        
(Showing Draft Content)

튜토리얼: 실시간 동시 작업 텍스트 편집기에 Presence 기능 추가

이 튜토리얼은 튜토리얼: 실시간 동시 작업 텍스트 편집기를 기반으로 하여 Presence 기능을 추가합니다. Presence는 온라인 사용자와 그들의 커서 위치를 표시하는 데 사용됩니다. 사용자는 에디터 내에서 다른 사용자들의 실시간 상태(예: 사용자 이름, 선택 영역 위치)를 확인할 수 있어 동시 작업 경험이 향상됩니다.

미리보기

한 사용자가 에디터에 문자를 입력하면, 다른 사용자들은 커서 위치, 선택 영역, 사용자 이름, 입력 내용을 실시간으로 확인할 수 있습니다.

text_editor_presence.6e26da.gif

사전 준비 사항

Step 1: 패키지 설치

프로젝트 루트 디렉터리에서 다음 명령어를 실행하여 Presence 관련 npm 패키지를 설치합니다.

npm install @mescius/js-collaboration-presence  @mescius/js-collaboration-presence-client
npm install quill-cursors

Step 2: 서버에 Presence 지원 추가

server.js를 수정하여 Presence 기능을 추가합니다.

import express from "express";
import { createServer } from "http";
import { Server } from "@mescius/js-collaboration";
import OT from "@mescius/js-collaboration-ot";
import { presenceFeature } from "@mescius/js-collaboration-presence";
import richText from "rich-text";

const app = express();
const httpServer = createServer(app);
const server = new Server({ httpServer });

// rich-text 타입 등록
OT.TypesManager.register(richText.type);

// OT 문서 서비스 초기화
server.useFeature(OT.documentFeature());

// presence 서비스 초기화
server.useFeature(presenceFeature());

// 정적 파일 제공
app.use(express.static("public"));

// 서버 시작
httpServer.listen(8080, () => {
  console.log("Server running at http://localhost:8080");
});

Step 3: 클라이언트에 Presence 지원 추가

온라인 사용자 커서 표시 기능을 추가하기 위해 클라이언트 파일을 수정합니다.

public/client.js를 업데이트합니다.

import { Client } from "@mescius/js-collaboration-client";
import * as OT from "@mescius/js-collaboration-ot-client";
import { Presence } from "@mescius/js-collaboration-presence-client";
import richText from "rich-text";
import Quill from "quill";
import QuillCursors from "quill-cursors";

Quill.register('modules/cursors', QuillCursors);

// rich-text 타입 등록
OT.TypesManager.register(richText.type);

// 서버에 연결하고 룸에 참여
const connection = new Client().connect("room-id");
const doc = new OT.SharedDoc(connection);
const quill = new Quill("#editor", { theme: "bubble", modules: { cursors: true } });
const cursors = quill.getModule('cursors');

// Presence 초기화
const presence = new Presence(connection);
const userId = `user-${Math.random().toString(36).substring(2, 9)}`; // 랜덤 사용자 ID 생성
const DEFAULT_COLOR_SCHEME = ['#0000ff', '#008000', '#9900cc', '#800000', '#00cc33', '#cc6600', '#cc0099'];

// 문서 구독
doc.subscribe().then(async () => {
    if (!doc.type) {
        try {
            await doc.create([{ insert: "Hi!" }], richText.type.uri, {});
        } catch (err) {
            console.error("Create Document Error:", err);
        }
    }
    quill.setContents(doc.data);
    quill.on("text-change", (delta, oldDelta, source) => {
        if (source !== "user") return;
        doc.submitOp(delta, { source: connection.id });
    });
    doc.on("op", (op, source) => {
        if (source === connection.id) return;
        quill.updateContents(op);

        // 콘텐츠 업데이트는 선택 영역 변경을 유발하지만,
        // Quill은 selection-change 이벤트를 트리거하지 않음
        presence.submitLocalStateField('selection', quill.getSelection());
    });
});

// 원격 프레즌스 표시
function updatePresenceList() {
    cursors.clearCursors();

    for (const id in presence.otherStates) {
        const data = presence.otherStates[id];
        cursors.createCursor(id, data.userId, data.color);
        cursors.moveCursor(id, data.selection);
    }
}


// Presence 업데이트 구독
presence.subscribe().then(() => {
    updatePresenceList();

    let color = DEFAULT_COLOR_SCHEME[Object.keys(presence.otherStates).length % DEFAULT_COLOR_SCHEME.length];

    presence.submitLocalState({ userId, selection: quill.getSelection(), color });

    // 선택 영역 변경 시 로컬 프레즌스 업데이트
    quill.on("selection-change", (range) => {
        presence.submitLocalStateField('selection', range);
    });

    presence.on("add", () => updatePresenceList());
    presence.on("update", () => updatePresenceList());
    presence.on("remove", () => updatePresenceList());
})

// 오류 처리
connection.on("error", (err) => console.error("Connection error:", err.message));
doc.on("error", (err) => console.error("Document error:", err.message));

Step 4: 실행 및 테스트

  1. 클라이언트 코드 번들링

    npm run build
  2. 서버 시작

    npm run start

    다음 출력이 표시되어야 합니다: Server running at http://localhost:8080.

  3. 기능 테스트

    1. 브라우저를 열고 http://localhost:8080에 접속합니다.

    2. 동일한 주소를 여러 창에서 열고 내용을 편집하여 실시간 동기화를 확인합니다.