# 셀 스레드 메모

## Content

스레드 메모는 사용자가 특정 셀에 컨텍스트 기반 토론을 추가하고 스레드 내에서 댓글에 답글을 달 수 있도록 합니다. 각 스레드는 답글로 구성된 대화 컨테이너 역할을 하여 팀이 문제를 효율적으로 논의, 검토 및 해결할 수 있도록 도와줍니다.
아래 예시는 팀원이 분기별 예산을 검토할 때 스레드 메모를 사용하는 방법을 보여줍니다.
![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.9b5959.png?width=600)

## 주요 장점

* 협업과 컨텍스트 기반 피드백을 가능하게 합니다.
* 답글에서 사용자 멘션과 하이퍼링크를 지원합니다.
* 이벤트 기반 통합 및 실행 취소/다시 실행(Undo/Redo)을 제공합니다.
* SpreadJS API 및 Excel 내보내기와 호환성을 유지합니다.

## 의존성

스레드 메모를 포함하려면 **올인원 번들**을 참조하거나, 필요한 **서브 모듈**을 개별적으로 로드할 수 있습니다.
**옵션 1 – 전체 패키지 사용**
전체 라이브러리 파일을 참조하는 경우:

```auto
<script src="gc.spread.sheets.all.<version>.min.js"></script>
```

**옵션 2 – 모듈 패키지 사용**
개별 모듈을 포함할 경우, 스레드 메모 기능을 활성화하려면 다음 패키지를 로드해야 합니다:

```auto
<script src="gc.spread.sheets.comments.<version>.min.js"></script>
<script src="gc.spread.sheets.components.<version>.min.js"></script>
<script src="gc.spread.sheets.threadedcomments.<version>.min.js"></script>
```

>type=note
> **참고:**
> 이 기능은 SpreadJS v19.0 이상에서 사용할 수 있습니다.

스레드 메모는 댓글을 작성하거나 답글을 작성하는 현재 사용자를 식별하기 위해 `GC.Spread.Common.UserManager` 구성요소에 의존합니다.
명시적으로 사용자를 구성하지 않으면 기본 *Guest* 사용자가 자동으로 생성됩니다.
전역적으로 사용자를 구성하고 관리하는 방법은 사용자 관리 기능을 참조하세요.

## 일반 메모 vs 스레드 메모

구조와 API를 살펴보기 전에, 스레드 메모가 SpreadJS의 기존 셀 메모와 어떻게 다른지 명확히 하겠습니다.

| **유형** | **설명** | **주요 사용** | **아이콘** | **예시** |
| --- | --- | ----- | --- | --- |
| 메모 | 셀에 첨부된 단일 텍스트 노트. 답글이나 멘션을 지원하지 않음. Excel의 “노트”와 유사. | 빠른 주석 또는 개발자 힌트 | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.00a2f3.png?width=10)<br>(API로 아이콘 색상/크기 변경 가능) | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.5b690f.png?width=500) |
| 스레드 메모 | 셀에 첨부된 토론 스레드로, 여러 답글, 멘션(`@user`), 상태 추적(해결/열림)을 지원. Excel의 “Comment”와 동일. | 협업, 검토 또는 다중 사용자 토론 | <ul><li>미해결:![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.15cc99.png?width=10)</li><li>해결:![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.8741c3.png?width=10)</li></ul>(고정 스타일, 자동 상태 색상) | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.46e4d6.png?width=500) |

>type=note
> **참고:**
>
> * Comment와 Threaded Comment는 동일한 워크북 내에서 공존할 수 있지만, 서로 다른 API와 모듈로 관리됩니다.
> * 셀에는 한 종류의 댓글만 포함될 수 있으며, 하나를 추가하면 다른 유형이 자동으로 대체됩니다.

## 아키텍처 개요

다음 표는 스레드 메모 아키텍처의 핵심 구성요소를 요약합니다.

| **구성요소** | **설명** | **그림** |
| ---- | --- | --- |
| Comment Manager | 시트의 모든 Threaded Comments를 관리. 스레드 생성 및 조회의 진입점 역할. | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.8bd971.png?width=500) |
| Thread | 특정 셀에 바인딩된 댓글 스레드. 여러 댓글을 보유하며 해결 상태를 추적. | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.dd5493.png?width=500) |
| Comment | 스레드 내 단일 메시지로, 메시지, 작성일, 작성자를 포함.작성자는 id, 이름, 성, 이메일, 아바타를 지원. | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.225dae.png?width=500) |
| Content Blocks | 메시지 콘텐츠의 내부 구조 정의: 텍스트, 멘션(@), 링크 블록. | ![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.521491.png?width=500) |

## 사용자 인터페이스 작업

이러한 작업은 UI를 통해 가능하며, 관련 API를 통해서도 접근할 수 있습니다.

| **작업** | **설명** |
| --- | --- |
| 스레드 메모 추가 | 선택된 셀에 새 메모 스레드를 생성합니다.<br>스레드에는 작성자 정보와 타임스탬프를 포함합니다. |
| 답글 | 기존 스레드에 답글을 추가합니다.<br>답글은 시간 순으로 추가됩니다. |
| 편집/업데이트 | 기존 메모나 답글을 수정합니다.<br>편집된 메시지는 원래 타임스탬프를 유지합니다. |
| 삭제 | 메모나 답글을 삭제합니다.<br>권한이 있는 사용자는 허용 시 다른 사용자의 항목도 삭제 가능합니다. |
| 해결/재개 | 스레드를 해결로 표시(새 답글 비활성화)하거나 추가 논의를 위해 재개합니다. |
| 복사/잘라내기/붙여넣기 | 셀을 복사, 잘라내기 또는 붙여넣을 때 스레드 메모를 유지합니다. |
| 실행 취소/다시 실행 | 마지막 추가, 편집 또는 삭제 작업을 취소하거나 재적용합니다. |

## 세부 기능

**이벤트**

| **이벤트** | **발생 시점** |
| --- | ----- |
| `ThreadedCommentChanging` | 스레드 또는 답글 업데이트 전 발생. |
| `ThreadedCommentChanged` | 스레드 또는 답글 업데이트 후 발생. |
| `UserMentioned` | 답글에 사용자가 멘션될 때 발생. |

**협업 모델**

* **편집자(Editors)**: 스레드 메모 추가, 편집, 삭제, 해결 가능.
* **뷰어(Viewers)**: 읽기 전용 접근 (조작 버튼이나 답글 편집기 없음).

**멘션(@)**

* 답글에서 `@username`으로 협업자 멘션 가능.
* 멘션이 추가되면 `UserMentioned` 이벤트 발생.
* 멘션 목록은 최대 **20명** 지원.

**해결 상태**
스레드는 *resolved* 플래그를 포함.

* **보라색 플래그:** 미해결
* **회색 플래그:** 해결됨

## 메모 동작 및 제한 사항

**편집 규칙**

* 여러 스레드 메모를 동시에 편집 가능.
* 저장되지 않은 편집 내용은 메모 숨김 시 삭제됨.
* 명시적 편집 상태 플래그는 유지되지 않음.

**멘션 및 링크**

* 멘션 클릭(**@user**) 시 사용자 정보 카드가 열리지 않음.
* 하이퍼링크 편집 동작은 브라우저별로 다름:
    * **Safari:** 게시 후 붙여넣거나 입력한 URL을 자동으로 하이퍼링크 변환.
    * **기타 브라우저:** **Space** 또는 **Enter** 입력 시 텍스트를 하이퍼링크로 변환.

**내보내기 및 호환성 제한**

* Excel로 내보낼 때 사용자 ID는 **UUID** 형식 준수 필요:
    `{XXXXXXXX‑XXXX‑XXXX‑XXXX‑XXXXXXXXXXXX}`
* 형식에 맞지 않는 사용자 ID는 SpreadJS가 Excel 규격에 맞게 조정.
    재가져오기 후 편집 권한이 손실될 수 있음.

**구조적 제한**
스레드 메모는 현재 **중첩 구조를 지원하지 않음**. 모든 답글은 단일(평면) 레벨에 위치.
**실시간 제한**
스레드 메모는 실시간 접속 상태나 라이브 편집 표시를 지원하지 않음.

## 단축키

스레드 메모 작업 시 사용 가능한 키보드 단축키:

| **단축키** | **기능** |
| --- | --- |
| Ctrl + Alt + M | 선택된 셀에 새 스레드 메모 삽입. |
| Ctrl + Enter | 편집 중인 답글 제출. |
| Enter | 선택한 댓글 스레드 확장/축소. |
| Esc | 답글 편집 취소, 변경 사항 저장하지 않음. |

>type=note
> **참고:** macOS에서는 **Ctrl** 대신 **Command (⌘)** 사용.

## 관련 API

스레드 메모는 SpreadJS 내 여러 핵심 클래스 및 이벤트에 의존.
**핵심 객체**

| **클래스** | **용도** |
| --- | --- |
| `GC.Spread.Sheets.ThreadedComments` | 워크시트 셀과 연결된 모든 메모 스레드 관리. |
| `GC.Spread.Sheets.ThreadedComments.ThreadedComment` | 단일 스레드 메모 또는 답글 표현; 콘텐츠 편집 및 해결 상태 표시. |
| `GC.Spread.Sheets.Events` | `ThreadedCommentChanging`, `ThreadedCommentChanged`, `UserMentioned` 등 댓글 관련 이벤트 노출. |
| `GC.Spread.Common.UserManager` | 댓글 작성자용 현재 사용자 구성 및 추적. |

**추가 지원**
`GC.Spread.Sheets.Search` 및 `GC.Spread.Sheets` 클래스는 검색, 복사, 잘라내기, 지우기, 사용 범위 등 일반 작업에 스레드 메모 데이터를 포함하도록 확장.

## 코드 예제 사용

다음 섹션은 SpreadJS API를 사용한 스레드 메모 기본 작업 예제를 보여줍니다.
**기본 예제 — 댓글 추가**
현재 사용자가 E3 셀에 새 스레드 메모를 생성합니다.
댓글 표시기가 자동으로 셀에 나타납니다.

```auto
// Initialize workbook
const spread = new GC.Spread.Sheets.Workbook("ss");
const sheet = spread.getActiveSheet();

// Define user info
var users = [
        { id: "user1", name: "Alice Johnson", email: "alice.johnson@company.com",},
        { id: "user2", name: "Bob Smith", email: "bob.smith@company.com",}
    ];
GC.Spread.Common.UserManager.configure({
    get: async (userId) => {
        if (userId === undefined) {
            return;
        }
        return new Promise((resolve) => {
            const user = users.find(u => u.id === userId);
            resolve(user);
        });
    },
    search: async (query) => {
        return new Promise((resolve) => {
            resolve(users.filter(u => 
                        u.name.toLowerCase().includes(query.toLowerCase()) || 
                        u.email.toLowerCase().includes(query.toLowerCase())
                    ));
        });
    }
});

// Set current user
GC.Spread.Common.UserManager.current("user1");

// Create a comment
const comment_1 = {
    message: [
       { type: GC.Spread.Sheets.ThreadedComments.ContentType.text, value: "Hey " },
       { type: GC.Spread.Sheets.ThreadedComments.ContentType.mention, userId: "user2" },
       { type: GC.Spread.Sheets.ThreadedComments.ContentType.text, value: ", please review the budget variance in Development department: " },
       { type: GC.Spread.Sheets.ThreadedComments.ContentType.link, href: "https://example.com/budget-policy", text: "Budget Policy" }
    ],
    authorId: "user1",
    createdAt: new Date("2025-12-10T15:44:00")
};

// Add a threaded comment to cell B4
const threadedCommentManager = sheet.threadedComments;
const threadedComment = threadedCommentManager.add(2, 4);
threadedComment.add(comment_1);
```

![image](https://gcdocumentsitekrblob.blob.core.windows.net/document-site-files/images/36b63360-fb01-457c-a448-c3ce32aaf454/image.ab684a.png?width=600)
**예제 - 스레드 메모 업데이트, 답글, 상태 변경, 삭제**
이 예제는 이전 예제를 기반으로 합니다.

```auto
// Get a specific thread
const thread = threadedCommentManager.get(2, 4);  
console.log(thread.row()); // Output: 2

// Create a reply
const reply_1 = {
    message: [
    {type: GC.Spread.Sheets.ThreadedComments.ContentType.text,
        value: "Confirmed. "
    }],
    authorId: "user2", 
    createdAt: new Date("2025-12-12T11:47:00")
};

// Add a reply
thread.add(reply_1);

// Edit an existing reply
thread.set(0,{ message: [{ type: GC.Spread.Sheets.ThreadedComments.ContentType.text, value: 'Please verify this value.' }] });

// Delete a reply
thread.remove(1);

// Resolve / reopen
thread.resolved(true)
```

**예제 - 스레드 메모 이벤트 처리**
이벤트를 구독하여 맞춤 로직을 트리거합니다.

```auto
sheet.bind(GC.Spread.Sheets.Events.ThreadedCommentChanging, function (e, info) {
    // Handle threaded comment property or reply changes
});

sheet.bind(GC.Spread.Sheets.Events.UserMentioned, function (e, info) {
    console.log(args);
});
```