Skip to content

Instantly share code, notes, and snippets.

@easylogic
Created November 24, 2025 08:27
Show Gist options
  • Select an option

  • Save easylogic/94c3ad1e591f6d8c8cbd9f856237649d to your computer and use it in GitHub Desktop.

Select an option

Save easylogic/94c3ad1e591f6d8c8cbd9f856237649d to your computer and use it in GitHub Desktop.
input-rendering-race-condition

์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง๊ณผ Selection ๋ณ€๊ฒฝ์˜ Race Condition ํ•ด๊ฒฐ

๋ฌธ์ œ ์ƒํ™ฉ

Race Condition ๋ฐœ์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค

์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ค‘:
1. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM์— ํ…์ŠคํŠธ ๋ณ€๊ฒฝ ์ ์šฉ (contentEditable)
   โ†“
2. MutationObserver๊ฐ€ ๋ณ€๊ฒฝ ๊ฐ์ง€
   โ†“
3. InputHandler๊ฐ€ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
   โ†“
4. editor:content.change ์ด๋ฒคํŠธ ๋ฐœ์ƒ
   โ†“
5. EditorViewDOM.render() ํ˜ธ์ถœ (๋™์‹œ์—)
   โ†“
6. Selection ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (๋™์‹œ์—)
   โ†“
โŒ ๋ฌธ์ œ: ๋ Œ๋”๋ง๊ณผ Selection ๋ณ€๊ฒฝ์ด ๋™์‹œ์— ๋ฐœ์ƒํ•˜์—ฌ ์ถฉ๋Œ

ํ•ต์‹ฌ ๋ฌธ์ œ:

  • ์ž…๋ ฅ ์ค‘์— ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๋ฉด DOM์ด ์žฌ์ƒ์„ฑ๋จ
  • ๋™์‹œ์— Selection์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ž˜๋ชป๋œ ์œ„์น˜๋กœ ์ด๋™
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฐฉํ•ด๋ฐ›๊ฑฐ๋‚˜ ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ

ํ˜„์žฌ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

1. skipRender ์˜ต์…˜์œผ๋กœ ์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง ์ฐจ๋‹จ

๊ตฌํ˜„ ์œ„์น˜: packages/editor-view-dom/src/event-handlers/input-handler.ts

// commitPendingImmediate()์—์„œ
this.editor.emit('editor:content.change', {
  skipRender: true, // ํ•„์ˆ˜: MutationObserver ๋ณ€๊ฒฝ์€ render() ํ˜ธ์ถœ ์•ˆ ํ•จ
  from: 'MutationObserver',
  transaction: { type: 'text_replace', nodeId }
});

๋™์ž‘:

  • MutationObserver์—์„œ ๊ฐ์ง€ํ•œ characterData ๋ณ€๊ฒฝ์€ skipRender: true๋กœ ์„ค์ •
  • ์ž…๋ ฅ ์ค‘์—๋Š” ๋ชจ๋ธ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  DOM ๋ Œ๋”๋ง์€ ํ•˜์ง€ ์•Š์Œ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์ง์ ‘ ์—…๋ฐ์ดํŠธํ–ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ๋ Œ๋”๋ง ๋ถˆํ•„์š”

2. editor:content.change ํ•ธ๋“ค๋Ÿฌ์—์„œ skipRender ์ฒดํฌ

๊ตฌํ˜„ ์œ„์น˜: packages/editor-view-dom/src/editor-view-dom.ts

this.editor.on('editor:content.change', (e: any) => {
  // ๋ Œ๋”๋ง ์ค‘์ด๋ฉด ๋ฌด์‹œ (๋ฌดํ•œ๋ฃจํ”„ ๋ฐฉ์ง€)
  if (this._isRendering) {
    return;
  }
  
  // skipRender: true์ธ ๊ฒฝ์šฐ ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ
  // MutationObserver์—์„œ ๊ฐ์ง€ํ•œ characterData ๋ณ€๊ฒฝ์€ ์ž…๋ ฅ ์ค‘์ด๋ฏ€๋กœ
  // ๋ Œ๋”๋ง์„ ์ง€์—ฐ์‹œ์ผœ selection๊ณผ์˜ race condition์„ ๋ฐฉ์ง€
  if (e?.skipRender) {
    return;
  }
  
  // ์™ธ๋ถ€ ๋ณ€๊ฒฝ(model-change)๋งŒ ๋ Œ๋”๋ง
  this.render();
});

๋™์ž‘:

  • skipRender: true์ธ ๊ฒฝ์šฐ ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋œ€
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ(model-change)๋งŒ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  • ์ž…๋ ฅ ์ค‘์—๋Š” ๋ Œ๋”๋ง๊ณผ Selection ๋ณ€๊ฒฝ์˜ race condition์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

3. ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง ์ œ๊ฑฐ

๊ตฌํ˜„ ์œ„์น˜: packages/editor-view-dom/src/editor-view-dom.ts

private _onInputEnd(): void {
  this._inputEndDebounceTimer = window.setTimeout(() => {
    // editingNodes ์ดˆ๊ธฐํ™”๋งŒ ์ˆ˜ํ–‰
    this._editingNodes.clear();
    // ์žฌ๋ Œ๋”๋ง์€ ํ•˜์ง€ ์•Š์Œ
    // - ์ž…๋ ฅ ์ค‘์—๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM์„ ์ง์ ‘ ์—…๋ฐ์ดํŠธ
    // - ์šฐ๋ฆฌ๋Š” ๋ชจ๋ธ๋งŒ ์—…๋ฐ์ดํŠธ (skipRender: true)
    // - ์ž…๋ ฅ์ด ๋๋‚œ ํ›„ ์žฌ๋ Œ๋”๋งํ•˜๋ฉด selection๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์Œ
  }, 500);
}

๋™์ž‘:

  • ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง์„ ํ•˜์ง€ ์•Š์Œ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์—…๋ฐ์ดํŠธํ–ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ๋ Œ๋”๋ง ๋ถˆํ•„์š”
  • ๋ชจ๋ธ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ์ด๋ฏธ ๋ฐ˜์˜๋˜์–ด ์žˆ์Œ

์ „์ฒด ํ๋ฆ„๋„

Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ: ์ž…๋ ฅ ์ค‘ (CharacterData ๋ณ€๊ฒฝ)

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant IH as InputHandler
    participant Editor as Editor
    participant EVD as EditorViewDOM

    User->>Browser: ํ‚ค๋ณด๋“œ ์ž…๋ ฅ
    Browser->>Browser: DOM ์—…๋ฐ์ดํŠธ (contentEditable)
    Browser->>Browser: Selection ์ž๋™ ์œ ์ง€
    Browser->>MO: characterData ๋ณ€๊ฒฝ ๊ฐ์ง€
    MO->>IH: handleTextContentChange()
    IH->>IH: DOM์—์„œ ์ „์ฒด ํ…์ŠคํŠธ ์žฌ๊ตฌ์„ฑ
    IH->>IH: handleEfficientEdit() - ๋ณ€๊ฒฝ ๋ถ„์„
    IH->>IH: Mark ๋ฒ”์œ„ ์ž๋™ ์กฐ์ •
    IH->>Editor: executeTransaction()
    Editor->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (ํ…์ŠคํŠธ + marks)
    Editor->>EVD: editor:content.change (skipRender: true)
    EVD->>EVD: skipRender ์ฒดํฌ
    EVD->>EVD: ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ โœ…
    Note over EVD: Selection ๋ณ€๊ฒฝ ์—†์Œ โœ…
Loading

Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ: ์™ธ๋ถ€ ๋ณ€๊ฒฝ (Model Change)

sequenceDiagram
    participant External as ์™ธ๋ถ€ (AI/๋™์‹œํŽธ์ง‘)
    participant Editor as Editor
    participant EVD as EditorViewDOM
    participant DOM as DOM

    External->>Editor: executeTransaction()
    Editor->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: skipRender ์ฒดํฌ
    EVD->>EVD: render() ํ˜ธ์ถœ โœ…
    EVD->>DOM: DOM ์—…๋ฐ์ดํŠธ
    EVD->>EVD: Selection ๋ณต์› โœ…
Loading

Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ: ์ž…๋ ฅ ์ƒํƒœ ์ „ํ™˜

stateDiagram-v2
    [*] --> Idle: ์ดˆ๊ธฐ ์ƒํƒœ
    
    Idle --> InputStart: ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์‹œ์ž‘
    InputStart --> Inputting: ์ž…๋ ฅ ์ค‘
    Inputting --> Inputting: ๊ณ„์† ์ž…๋ ฅ (skipRender: true)
    Inputting --> InputEnd: ์ž…๋ ฅ ์ผ์‹œ์ •์ง€ (500ms)
    InputEnd --> Idle: editingNodes ์ดˆ๊ธฐํ™”
    
    Idle --> ExternalChange: ์™ธ๋ถ€ ๋ณ€๊ฒฝ ๊ฐ์ง€
    ExternalChange --> Rendering: render() ํ˜ธ์ถœ
    Rendering --> Idle: ๋ Œ๋”๋ง ์™„๋ฃŒ
    
    Inputting --> IMEComposing: IME ์กฐํ•ฉ ์‹œ์ž‘
    IMEComposing --> IMEComposing: ์กฐํ•ฉ ์ค‘ (skipRender: true)
    IMEComposing --> Inputting: ์กฐํ•ฉ ์™„๋ฃŒ
Loading

Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ: ์˜์‚ฌ๊ฒฐ์ • ํ๋ฆ„

flowchart TD
    Start([editor:content.change ์ด๋ฒคํŠธ]) --> CheckRendering{๋ Œ๋”๋ง ์ค‘?}
    CheckRendering -->|Yes| Skip1[๊ฑด๋„ˆ๋œ€]
    CheckRendering -->|No| CheckSkipRender{skipRender?}
    CheckSkipRender -->|true| CheckFrom{from?}
    CheckFrom -->|MutationObserver| Skip2[๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ โœ…]
    CheckFrom -->|model-change| Render[render() ํ˜ธ์ถœ โœ…]
    CheckSkipRender -->|false| Render
    CheckSkipRender -->|undefined| Render
    
    Skip1 --> End([์ข…๋ฃŒ])
    Skip2 --> End
    Render --> End
    
    style Skip2 fill:#90EE90
    style Render fill:#90EE90
    style Skip1 fill:#FFB6C1
Loading

์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋™์ž‘ ํ‘œ

์‹œ๋‚˜๋ฆฌ์˜ค ์ด๋ฒคํŠธ ์†Œ์Šค skipRender ๋ Œ๋”๋ง Selection ๊ฒฐ๊ณผ
๊ธฐ๋ณธ ์ž…๋ ฅ
์‚ฌ์šฉ์ž ์ž…๋ ฅ (characterData) MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… Race condition ์—†์Œ
์‚ฌ์šฉ์ž ์ž…๋ ฅ + Selection ์ด๋™ (Shift+Arrow) MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… Selection ์ด๋™์ด ๋ธŒ๋ผ์šฐ์ €์— ์˜ํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ์ ์šฉ
๋ฐฑ์ŠคํŽ˜์ด์Šค/Delete ํ‚ค ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํ…์ŠคํŠธ ์‚ญ์ œ ํ›„ Selection ์œ ์ง€
IME ์ž…๋ ฅ
IME ์กฐํ•ฉ ์ค‘ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์กฐํ•ฉ ์™„๋ฃŒ ํ›„ ์ฒ˜๋ฆฌ
IME ์กฐํ•ฉ ์™„๋ฃŒ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ตœ์ข… ํ…์ŠคํŠธ๋งŒ ๋ชจ๋ธ ๋ฐ˜์˜
์™ธ๋ถ€ ๋ณ€๊ฒฝ
์™ธ๋ถ€ ๋ณ€๊ฒฝ (model-change) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… ์ •์ƒ ๋™์ž‘
์™ธ๋ถ€ Decorator ๋ณ€๊ฒฝ (์˜ˆ: ๋Œ“๊ธ€, AI ๊ฐ•์กฐ) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… Marks/Decorators ์—…๋ฐ์ดํŠธ ํ›„ Selection ์œ ์ง€
์™ธ๋ถ€ Selection ๋™๊ธฐํ™” (ํ˜‘์—… ์‚ฌ์šฉ์ž) Editor false โœ… ์ˆ˜ํ–‰ ํ›„ Selection ๋ณต์› โœ… ๋กœ์ปฌ Selection ๋ณด์กด, remote Selection์€ ๋ณ„๋„ ๋ ˆ์ด์–ด์— ๋ฐ˜์˜ โœ… ํ˜‘์—… ์‹œ Selection ์ถฉ๋Œ ์—†์Œ
๋ณต์‚ฌ/๋ถ™์—ฌ๋„ฃ๊ธฐ
๋ถ™์—ฌ๋„ฃ๊ธฐ (paste) โ†’ ํ…์ŠคํŠธ๋งŒ ๋ณ€๊ฒฝ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํ…์ŠคํŠธ๋งŒ ์ถ”๊ฐ€, Selection ์œ ์ง€
๋ถ™์—ฌ๋„ฃ๊ธฐ (paste) โ†’ DOM ๊ตฌ์กฐ ๋ณ€๊ฒฝ MutationObserver true (ํ…์ŠคํŠธ), false (๊ตฌ์กฐ) ํ…์ŠคํŠธ๋งŒ ๋ณ€๊ฒฝ ์‹œ โŒ, ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ โœ… ํ…์ŠคํŠธ ๋ณ€๊ฒฝ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์œ ์ง€, ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ ๋ Œ๋” ํ›„ ๋ณต์› โœ… paste ๋‚ด์šฉ์— ๋”ฐ๋ผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
๋ณต์‚ฌ (copy) - - - โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ โœ… Selection ๊ธฐ๋ฐ˜ ๋ณต์‚ฌ
์„ ํƒ ๋ฐ ์ž…๋ ฅ
๋“œ๋ž˜๊ทธ ์„ ํƒ ํ›„ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ๋“œ๋ž˜๊ทธ ์„ ํƒ ์˜์—ญ์ด ๊ทธ๋Œ€๋กœ ์œ ์ง€๋œ ์ƒํƒœ์—์„œ ์ž…๋ ฅ ๋ฐ˜์˜
Range ์„ ํƒ ํ›„ ์ž…๋ ฅ (ํ…์ŠคํŠธ ๊ต์ฒด) MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์„ ํƒ๋œ ํ…์ŠคํŠธ๊ฐ€ ์‚ญ์ œ๋˜๊ณ  ์ƒˆ ํ…์ŠคํŠธ ์‚ฝ์ž…
Mark ํ† ๊ธ€
Bold/Italic ํ† ๊ธ€ (Mod+B, Mod+I) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… Mark ์ ์šฉ ํ›„ Selection ์œ ์ง€
Mark ํ† ๊ธ€ ์ค‘ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ž…๋ ฅ๊ณผ Mark ํ† ๊ธ€์ด ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
Undo/Redo
Undo (Mod+Z) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… ์ด์ „ ์ƒํƒœ๋กœ ๋ณต์›, Selection ๋ณต์›
Redo (Mod+Shift+Z) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… ๋‹ค์Œ ์ƒํƒœ๋กœ ๋ณต์›, Selection ๋ณต์›
Undo ์ค‘ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ž…๋ ฅ์ด ์šฐ์„ , Undo๋Š” ์ทจ์†Œ
๋‹ค์ค‘ ์ž…๋ ฅ
์—ฌ๋Ÿฌ ๋…ธ๋“œ ๋™์‹œ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ๊ฐ ๋…ธ๋“œ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
์ž…๋ ฅ ์ค‘ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ํฌ์ปค์Šค ์ด๋™ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํฌ์ปค์Šค ์ด๋™ ํ›„ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
Cross-node Selection
์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection ํ›„ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์— ์ž…๋ ฅ, Selection ์ถ•์†Œ
์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection ํ›„ ์‚ญ์ œ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์„ ํƒ๋œ ๋ชจ๋“  ๋…ธ๋“œ์˜ ํ…์ŠคํŠธ ์‚ญ์ œ
ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ
Home/End ํ‚ค (๋ผ์ธ ์‹œ์ž‘/๋ ์ด๋™) - - - โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ โœ… Selection๋งŒ ์ด๋™, ์ž…๋ ฅ ์—†์Œ
PageUp/PageDown ํ‚ค (ํŽ˜์ด์ง€ ์ด๋™) - - - โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ โœ… Selection๋งŒ ์ด๋™, ์ž…๋ ฅ ์—†์Œ
Ctrl+Arrow (๋‹จ์–ด ๋‹จ์œ„ ์ด๋™) - - - โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ โœ… Selection๋งŒ ์ด๋™, ์ž…๋ ฅ ์—†์Œ
ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ ํ›„ ์ฆ‰์‹œ ํ…์ŠคํŠธ ์ž…๋ ฅ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํŠน์ˆ˜ ํ‚ค ์ด๋™ ํ›„ ์ž…๋ ฅ ์ •์ƒ ์ฒ˜๋ฆฌ
ํฌ์ปค์Šค ๋ฐ ํƒญ
์ž…๋ ฅ ์ค‘ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ํฌ์ปค์Šค ์ด๋™ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํฌ์ปค์Šค ์ด๋™ ํ›„ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
์ž…๋ ฅ ์ค‘ ๋‹ค๋ฅธ ํƒญ์œผ๋กœ ์ „ํ™˜ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํƒญ ์ „ํ™˜ ์‹œ ์ž…๋ ฅ ์ค‘๋‹จ, ๋ณต๊ท€ ์‹œ ์žฌ๊ฐœ
์ž…๋ ฅ ์ค‘ ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ „ํ™˜ ํ›„ ๋ณต๊ท€ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ํฌ์ปค์Šค ๋ณต๊ท€ ์‹œ ์ž…๋ ฅ ์ƒํƒœ ์œ ์ง€
๋น„ ํ…์ŠคํŠธ ์š”์†Œ
์ด๋ฏธ์ง€ ์‚ฝ์ž… (drag & drop) Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… ์ด๋ฏธ์ง€ ์‚ฝ์ž… ํ›„ Selection ๋ณต์›
์ด๋ฏธ์ง€ ์‚ฝ์ž… (paste) MutationObserver false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… ์ด๋ฏธ์ง€ ๋ถ™์—ฌ๋„ฃ๊ธฐ ํ›„ Selection ๋ณต์›
Embed ์š”์†Œ ์‚ฝ์ž… Editor false โœ… ์ˆ˜ํ–‰ โœ… ๋ณต์› โœ… Embed ์‚ฝ์ž… ํ›„ Selection ๋ณต์›
์Šคํฌ๋กค ๋ฐ ๋ ˆ์ด์•„์›ƒ
์ž…๋ ฅ ์ค‘ ์Šคํฌ๋กค ๋ฐœ์ƒ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์Šคํฌ๋กค๊ณผ ์ž…๋ ฅ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
์ž…๋ ฅ ์ค‘ ์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ๊ณผ ์ž…๋ ฅ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
์ž…๋ ฅ ์ค‘ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ํŠธ๋ฆฌ๊ฑฐ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์Šคํƒ€์ผ ๋ณ€๊ฒฝ๊ณผ ์ž…๋ ฅ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
์—๋Ÿฌ ๋ฐ ์˜ˆ์™ธ
์ž…๋ ฅ ์ค‘ ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ (ํ˜‘์—…) MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ๋กœ์ปฌ ์ž…๋ ฅ์€ ์ •์ƒ ์ฒ˜๋ฆฌ, ๋™๊ธฐํ™”๋Š” ์žฌ์‹œ๋„
์ž…๋ ฅ ์ค‘ ๋ชจ๋ธ ๊ฒ€์ฆ ์‹คํŒจ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ž…๋ ฅ์€ ์œ ์ง€, ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ๋งŒ ์‹คํŒจ
์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง ์—๋Ÿฌ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ž…๋ ฅ์€ ์ •์ƒ, ์—๋Ÿฌ๋Š” ๋ณ„๋„ ์ฒ˜๋ฆฌ
ํŠน์ˆ˜ ์ผ€์ด์Šค
์ž…๋ ฅ ์ข…๋ฃŒ - - โŒ ์žฌ๋ Œ๋”๋ง ์—†์Œ โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ์ถฉ๋Œ ์—†์Œ
์ž…๋ ฅ ์ค‘ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ๊ฐ์ง€ Editor false โœ… ์ˆ˜ํ–‰ (๋‹ค๋ฅธ ๋…ธ๋“œ) โœ… ๋ณต์› โœ… ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋Š” ๋ณดํ˜ธ, ๋‹ค๋ฅธ ๋…ธ๋“œ๋Š” ์—…๋ฐ์ดํŠธ
๋ Œ๋”๋ง ์ค‘ ์ž…๋ ฅ ๊ฐ์ง€ MutationObserver true โŒ ๊ฑด๋„ˆ๋œ€ (๋ Œ๋”๋ง ์ค‘ ์ฒดํฌ) โœ… ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ง€ โœ… ๋ฌดํ•œ๋ฃจํ”„ ๋ฐฉ์ง€
์ž…๋ ฅ ์ค‘ ๋™์ผ ๋…ธ๋“œ ์™ธ๋ถ€ ๋ณ€๊ฒฝ Editor false โœ… ์ˆ˜ํ–‰ โš ๏ธ โœ… ๋ณต์› โš ๏ธ ํ˜„์žฌ๋Š” ์ถฉ๋Œ ๊ฐ€๋Šฅ (ํ–ฅํ›„ skipNodes๋กœ ๋ณดํ˜ธ ํ•„์š”)

์ƒ์„ธ ์‹œ๋‚˜๋ฆฌ์˜ค ์„ค๋ช…

์‹œ๋‚˜๋ฆฌ์˜ค 1: ์‚ฌ์šฉ์ž ์ž…๋ ฅ (characterData)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM์— ํ…์ŠคํŠธ ์ถ”๊ฐ€
  2. MutationObserver๊ฐ€ characterData ๋ณ€๊ฒฝ ๊ฐ์ง€
  3. InputHandler๊ฐ€ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
  4. skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ
  5. Selection์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์œ ์ง€

๊ฒฐ๊ณผ: โœ… Race condition ์—†์Œ, ์ž…๋ ฅ์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ง„ํ–‰๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 2: Selection ์ด๋™ ์ค‘ ์ž…๋ ฅ (Shift+Arrow)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ Shift+Arrow๋กœ ํ…์ŠคํŠธ๋ฅผ ์„ ํƒํ•˜๋ฉด์„œ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํŠน์ง•:

  • Selection ๋ณ€๊ฒฝ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌ
  • ์ž…๋ ฅ๊ณผ Selection ๋ณ€๊ฒฝ์ด ๋™์‹œ์— ๋ฐœ์ƒํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
  • skipRender: true๋กœ ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„ ์ถฉ๋Œ ์—†์Œ

๊ฒฐ๊ณผ: โœ… Selection ์ด๋™๊ณผ ์ž…๋ ฅ์ด ๋ชจ๋‘ ์ •์ƒ ๋™์ž‘

์‹œ๋‚˜๋ฆฌ์˜ค 3: ๋ฐฑ์ŠคํŽ˜์ด์Šค/Delete ํ‚ค ์ž…๋ ฅ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฑ์ŠคํŽ˜์ด์Šค ๋˜๋Š” Delete ํ‚ค๋กœ ํ…์ŠคํŠธ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM์—์„œ ํ…์ŠคํŠธ ์‚ญ์ œ
  2. MutationObserver๊ฐ€ characterData ๋ณ€๊ฒฝ ๊ฐ์ง€
  3. InputHandler๊ฐ€ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
  4. skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ
  5. Selection์€ ์‚ญ์ œ ํ›„ ์œ„์น˜๋กœ ์ž๋™ ์ด๋™

๊ฒฐ๊ณผ: โœ… ํ…์ŠคํŠธ ์‚ญ์ œ ํ›„ Selection์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 4: IME ์กฐํ•ฉ ์ค‘

์ƒํ™ฉ: ํ•œ๊ตญ์–ด, ์ผ๋ณธ์–ด, ์ค‘๊ตญ์–ด ๋“ฑ IME๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํŠน์ง•:

  • ์กฐํ•ฉ ์ค‘๊ฐ„ ์ƒํƒœ (์˜ˆ: "ใ…Žใ…ใ„ด" โ†’ "ํ•œ")๋Š” ์—ฌ๋Ÿฌ ๋ฒˆ DOM ๋ณ€๊ฒฝ ๋ฐœ์ƒ
  • ๋ชจ๋“  ๋ณ€๊ฒฝ์— skipRender: true ์ ์šฉ
  • ์กฐํ•ฉ ์™„๋ฃŒ ํ›„ ์ตœ์ข… ํ…์ŠคํŠธ๋งŒ ๋ชจ๋ธ์— ๋ฐ˜์˜

๊ฒฐ๊ณผ: โœ… ์กฐํ•ฉ ์ค‘ ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„ ๋ถ€๋“œ๋Ÿฌ์šด ์ž…๋ ฅ ๊ฒฝํ—˜

์‹œ๋‚˜๋ฆฌ์˜ค 5: ๋ถ™์—ฌ๋„ฃ๊ธฐ (Paste)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์—์„œ ํ…์ŠคํŠธ๋ฅผ ๋ถ™์—ฌ๋„ฃ๋Š” ๊ฒฝ์šฐ

์ผ€์ด์Šค A: ๋‹จ์ˆœ ํ…์ŠคํŠธ ๋ถ™์—ฌ๋„ฃ๊ธฐ

  • MutationObserver๊ฐ€ characterData ๋ณ€๊ฒฝ ๊ฐ์ง€
  • skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM๊ณผ Selection์„ ์ง์ ‘ ์ฒ˜๋ฆฌ

์ผ€์ด์Šค B: ๋ณต์žกํ•œ ๊ตฌ์กฐ ๋ถ™์—ฌ๋„ฃ๊ธฐ (HTML, Mark ๋“ฑ)

  • DOM ๊ตฌ์กฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Œ
  • ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  • Selection์€ ๋ Œ๋”๋ง ํ›„ ๋ณต์›

๊ฒฐ๊ณผ: โœ… ๋ถ™์—ฌ๋„ฃ๊ธฐ ๋‚ด์šฉ์— ๋”ฐ๋ผ ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜ค 6: ๋“œ๋ž˜๊ทธ ์„ ํƒ ํ›„ ์ž…๋ ฅ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค๋กœ ํ…์ŠคํŠธ๋ฅผ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์„ ํƒํ•œ ํ›„ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ๋“œ๋ž˜๊ทธ๋กœ ํ…์ŠคํŠธ ์„ ํƒ (๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ)
  2. ์„ ํƒ๋œ ์˜์—ญ์— ํ…์ŠคํŠธ ์ž…๋ ฅ
  3. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„ ํƒ๋œ ํ…์ŠคํŠธ๋ฅผ ์‚ญ์ œํ•˜๊ณ  ์ƒˆ ํ…์ŠคํŠธ ์‚ฝ์ž…
  4. MutationObserver๊ฐ€ ๋ณ€๊ฒฝ ๊ฐ์ง€
  5. skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ

๊ฒฐ๊ณผ: โœ… ์„ ํƒ๋œ ์˜์—ญ์ด ๊ต์ฒด๋˜๊ณ  Selection์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 7: Mark ํ† ๊ธ€ (Bold/Italic)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ Mod+B ๋˜๋Š” Mod+I๋กœ Mark๋ฅผ ํ† ๊ธ€ํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ๊ฐ์ง€
  2. Editor๊ฐ€ ๋ชจ๋ธ์—์„œ Mark ํ† ๊ธ€
  3. skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  4. DOM์ด Mark ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธ
  5. Selection ๋ณต์›

ํŠน์ง•:

  • Mark ํ† ๊ธ€์€ Editor ๋ ˆ๋ฒจ์—์„œ ์ฒ˜๋ฆฌ
  • ๋ Œ๋”๋ง์ด ํ•„์š”ํ•˜๋ฏ€๋กœ skipRender: false
  • ์ž…๋ ฅ๊ณผ๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ

๊ฒฐ๊ณผ: โœ… Mark๊ฐ€ ์ ์šฉ๋˜๊ณ  Selection์ด ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 8: Undo/Redo

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ Mod+Z๋กœ Undo ๋˜๋Š” Mod+Shift+Z๋กœ Redoํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ๊ฐ์ง€
  2. Editor๊ฐ€ ํžˆ์Šคํ† ๋ฆฌ์—์„œ ์ด์ „/๋‹ค์Œ ์ƒํƒœ ๋ณต์›
  3. skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  4. DOM์ด ์ด์ „/๋‹ค์Œ ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธ
  5. Selection ๋ณต์›

ํŠน์ง•:

  • ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ์ƒํƒœ ๋ณต์›
  • ๋ Œ๋”๋ง์ด ํ•„์š”ํ•˜๋ฏ€๋กœ skipRender: false
  • Selection๋„ ํ•จ๊ป˜ ๋ณต์›

๊ฒฐ๊ณผ: โœ… ์ด์ „/๋‹ค์Œ ์ƒํƒœ๋กœ ๋ณต์›๋˜๊ณ  Selection์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 9: ์ž…๋ ฅ ์ค‘ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ๊ฐ์ง€

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘์ผ ๋•Œ AI๋‚˜ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ™์€ ๋…ธ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ

ํ˜„์žฌ ๊ตฌํ˜„:

  • ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋Š” _editingNodes์— ์ถ”๊ฐ€๋จ
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  • ํ•˜์ง€๋งŒ ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋Š” ๋ณดํ˜ธ๋˜์ง€ ์•Š์Œ (ํ–ฅํ›„ ๊ฐœ์„  ํ•„์š”)

ํ–ฅํ›„ ๊ฐœ์„  ๋ฐฉ์•ˆ:

  • skipNodes ์˜ต์…˜์œผ๋กœ ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ ๋ณดํ˜ธ
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ pending์— ์ €์žฅ ํ›„ ์ž…๋ ฅ ์™„๋ฃŒ ์‹œ ์ ์šฉ

๊ฒฐ๊ณผ: โš ๏ธ ํ˜„์žฌ๋Š” ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ (ํ–ฅํ›„ ๊ฐœ์„  ํ•„์š”)

์‹œ๋‚˜๋ฆฌ์˜ค 10: ์—ฌ๋Ÿฌ ๋…ธ๋“œ ๋™์‹œ ์ž…๋ ฅ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ๋…ธ๋“œ์—์„œ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํŠน์ง•:

  • ๊ฐ ๋…ธ๋“œ์˜ ์ž…๋ ฅ์€ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
  • ๊ฐ๊ฐ skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ
  • _editingNodes์— ์—ฌ๋Ÿฌ ๋…ธ๋“œ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Œ

๊ฒฐ๊ณผ: โœ… ๊ฐ ๋…ธ๋“œ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์ถฉ๋Œ ์—†์Œ

์‹œ๋‚˜๋ฆฌ์˜ค 11: Cross-node Selection ์ž…๋ ฅ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ inline-text ๋…ธ๋“œ์— ๊ฑธ์ณ ํ…์ŠคํŠธ๋ฅผ ์„ ํƒํ•œ ํ›„ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection ์ƒ์„ฑ (๋“œ๋ž˜๊ทธ ๋˜๋Š” Shift+Arrow)
  2. ์„ ํƒ๋œ ์˜์—ญ์— ํ…์ŠคํŠธ ์ž…๋ ฅ
  3. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์— ์ž…๋ ฅํ•˜๊ณ  Selection ์ถ•์†Œ
  4. MutationObserver๊ฐ€ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์˜ ๋ณ€๊ฒฝ๋งŒ ๊ฐ์ง€
  5. skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ

ํŠน์ง•:

  • ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ
  • ์ž…๋ ฅ์€ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์—๋งŒ ์ ์šฉ๋จ
  • Selection์€ ์ž…๋ ฅ ํ›„ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์˜ ์ž…๋ ฅ ์œ„์น˜๋กœ ์ถ•์†Œ

๊ฒฐ๊ณผ: โœ… ์—ฌ๋Ÿฌ ๋…ธ๋“œ Selection ํ›„ ์ž…๋ ฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 12: ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ (Home/End, Ctrl+Arrow)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ Home, End, Ctrl+Arrow ๋“ฑ Selection๋งŒ ์ด๋™์‹œํ‚ค๋Š” ํ‚ค๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ

ํŠน์ง•:

  • ํŠน์ˆ˜ ํ‚ค๋Š” DOM ๋ณ€๊ฒฝ์„ ์ผ์œผํ‚ค์ง€ ์•Š์Œ
  • Selection๋งŒ ์ด๋™
  • MutationObserver๊ฐ€ ๊ฐ์ง€ํ•˜์ง€ ์•Š์Œ
  • editor:content.change ์ด๋ฒคํŠธ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ ํ›„ ์ฆ‰์‹œ ํ…์ŠคํŠธ ์ž…๋ ฅ:

  1. ํŠน์ˆ˜ ํ‚ค๋กœ Selection ์ด๋™
  2. ์ฆ‰์‹œ ํ…์ŠคํŠธ ์ž…๋ ฅ
  3. MutationObserver๊ฐ€ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ๋งŒ ๊ฐ์ง€
  4. skipRender: true๋กœ ๋ Œ๋”๋ง ์ฐจ๋‹จ

๊ฒฐ๊ณผ: โœ… ํŠน์ˆ˜ ํ‚ค ์ด๋™ ํ›„ ์ž…๋ ฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 13: ์ž…๋ ฅ ์ค‘ ํฌ์ปค์Šค ์ด๋™

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘์— ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ํฌ์ปค์Šค๋ฅผ ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ๋…ธ๋“œ A์—์„œ ์ž…๋ ฅ ์ค‘
  2. ๋งˆ์šฐ์Šค ํด๋ฆญ ๋˜๋Š” ํ‚ค๋ณด๋“œ๋กœ ๋…ธ๋“œ B๋กœ ํฌ์ปค์Šค ์ด๋™
  3. ๋…ธ๋“œ B์—์„œ ์ž…๋ ฅ ์‹œ์ž‘
  4. ๊ฐ ๋…ธ๋“œ์˜ ์ž…๋ ฅ์€ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ

ํŠน์ง•:

  • ํฌ์ปค์Šค ์ด๋™์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ
  • ์ด์ „ ๋…ธ๋“œ์˜ ์ž…๋ ฅ์€ ์™„๋ฃŒ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ
  • ์ƒˆ ๋…ธ๋“œ์˜ ์ž…๋ ฅ์€ ์ƒˆ๋กœ์šด ์ž…๋ ฅ์œผ๋กœ ์ฒ˜๋ฆฌ

๊ฒฐ๊ณผ: โœ… ํฌ์ปค์Šค ์ด๋™ ํ›„ ์ž…๋ ฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 14: ์ž…๋ ฅ ์ค‘ ํƒญ ์ „ํ™˜

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘์— ๋ธŒ๋ผ์šฐ์ € ํƒญ์„ ์ „ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. ๋…ธ๋“œ์—์„œ ์ž…๋ ฅ ์ค‘
  2. ๋‹ค๋ฅธ ํƒญ์œผ๋กœ ์ „ํ™˜ (Alt+Tab ๋˜๋Š” ํƒญ ํด๋ฆญ)
  3. ์ž…๋ ฅ์ด ์ผ์‹œ ์ค‘๋‹จ๋จ
  4. ์›๋ž˜ ํƒญ์œผ๋กœ ๋ณต๊ท€
  5. ์ž…๋ ฅ ์žฌ๊ฐœ ๊ฐ€๋Šฅ

ํŠน์ง•:

  • ํƒญ ์ „ํ™˜ ์‹œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํฌ์ปค์Šค๋ฅผ ์žƒ์Œ
  • ์ž…๋ ฅ์€ ์ผ์‹œ ์ค‘๋‹จ๋˜์ง€๋งŒ DOM์€ ์œ ์ง€๋จ
  • ํƒญ ๋ณต๊ท€ ์‹œ ํฌ์ปค์Šค์™€ Selection์ด ์œ ์ง€๋จ

๊ฒฐ๊ณผ: โœ… ํƒญ ์ „ํ™˜ ํ›„ ๋ณต๊ท€ ์‹œ ์ž…๋ ฅ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 15: ๋น„ ํ…์ŠคํŠธ ์š”์†Œ ์‚ฝ์ž… (์ด๋ฏธ์ง€, Embed)

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ์ง€๋‚˜ Embed ์š”์†Œ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒฝ์šฐ

์ผ€์ด์Šค A: Drag & Drop์œผ๋กœ ์ด๋ฏธ์ง€ ์‚ฝ์ž…

  1. ์ด๋ฏธ์ง€๋ฅผ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์—๋””ํ„ฐ์— ๋“œ๋กญ
  2. Editor๊ฐ€ ๋ชจ๋ธ์— ์ด๋ฏธ์ง€ ๋…ธ๋“œ ์ถ”๊ฐ€
  3. skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  4. DOM์— ์ด๋ฏธ์ง€ ์š”์†Œ ์ถ”๊ฐ€
  5. Selection ๋ณต์›

์ผ€์ด์Šค B: Paste๋กœ ์ด๋ฏธ์ง€ ์‚ฝ์ž…

  1. ํด๋ฆฝ๋ณด๋“œ์—์„œ ์ด๋ฏธ์ง€ ๋ถ™์—ฌ๋„ฃ๊ธฐ
  2. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ DOM์— ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
  3. MutationObserver๊ฐ€ childList ๋ณ€๊ฒฝ ๊ฐ์ง€
  4. Editor๊ฐ€ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
  5. skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰ (๊ตฌ์กฐ ๋ณ€๊ฒฝ)

๊ฒฐ๊ณผ: โœ… ์ด๋ฏธ์ง€/Embed ์‚ฝ์ž… ํ›„ Selection์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์œ ์ง€๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 16: ์ž…๋ ฅ ์ค‘ ์Šคํฌ๋กค/๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘์— ์Šคํฌ๋กคํ•˜๊ฑฐ๋‚˜ ์ฐฝ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ

ํŠน์ง•:

  • ์Šคํฌ๋กค์€ DOM ๋ณ€๊ฒฝ์„ ์ผ์œผํ‚ค์ง€ ์•Š์Œ
  • MutationObserver๊ฐ€ ๊ฐ์ง€ํ•˜์ง€ ์•Š์Œ
  • ์ž…๋ ฅ๊ณผ ์Šคํฌ๋กค์ด ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ž…๋ ฅ ์˜์—ญ์„ ํ™”๋ฉด์— ์œ ์ง€

์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ:

  • ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ ๋ฐœ์ƒ
  • ํ•˜์ง€๋งŒ DOM ๊ตฌ์กฐ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
  • ์ž…๋ ฅ์€ ๊ณ„์† ์ง„ํ–‰๋จ

๊ฒฐ๊ณผ: โœ… ์Šคํฌ๋กค/๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ๊ณผ ์ž…๋ ฅ์ด ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 17: ์ž…๋ ฅ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ

์ƒํ™ฉ: ์ž…๋ ฅ ์ค‘์— ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ๋‚˜ ๋ชจ๋ธ ๊ฒ€์ฆ ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ

์ผ€์ด์Šค A: ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ (ํ˜‘์—…)

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘
  2. ํ˜‘์—… ์„œ๋ฒ„์™€์˜ ๋™๊ธฐํ™” ์‹คํŒจ
  3. ๋กœ์ปฌ ์ž…๋ ฅ์€ ์ •์ƒ ์ฒ˜๋ฆฌ (skipRender: true)
  4. ๋™๊ธฐํ™”๋Š” ์žฌ์‹œ๋„ ํ์— ์ถ”๊ฐ€

์ผ€์ด์Šค B: ๋ชจ๋ธ ๊ฒ€์ฆ ์‹คํŒจ

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘
  2. ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ ์‹œ ๊ฒ€์ฆ ์‹คํŒจ
  3. ์ž…๋ ฅ์€ DOM์— ๋ฐ˜์˜๋จ (๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฒ˜๋ฆฌ)
  4. ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ๋งŒ ์‹คํŒจ
  5. ์‚ฌ์šฉ์ž๋Š” ์ž…๋ ฅ์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์Œ

๊ฒฐ๊ณผ: โœ… ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ์—๋„ ์ž…๋ ฅ์€ ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ

์‹œ๋‚˜๋ฆฌ์˜ค 18: ์ž…๋ ฅ ์ค‘ ๋™์ผ ๋…ธ๋“œ ์™ธ๋ถ€ ๋ณ€๊ฒฝ

์ƒํ™ฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ์ค‘์ผ ๋•Œ AI๋‚˜ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ™์€ ๋…ธ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ

ํ˜„์žฌ ๊ตฌํ˜„:

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋…ธ๋“œ A์—์„œ ์ž…๋ ฅ ์ค‘ (_editingNodes์— ์ถ”๊ฐ€)
  2. AI๊ฐ€ ๋…ธ๋“œ A๋ฅผ ๋ณ€๊ฒฝ
  3. skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  4. ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋„ ์—…๋ฐ์ดํŠธ๋จ โš ๏ธ

๋ฌธ์ œ์ :

  • ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๊ฐ€ ์™ธ๋ถ€ ๋ณ€๊ฒฝ์œผ๋กœ ๋ฎ์–ด์”Œ์›Œ์งˆ ์ˆ˜ ์žˆ์Œ
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ

ํ–ฅํ›„ ๊ฐœ์„  ๋ฐฉ์•ˆ:

  • skipNodes ์˜ต์…˜์œผ๋กœ ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ ๋ณดํ˜ธ
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ pending์— ์ €์žฅ
  • ์ž…๋ ฅ ์™„๋ฃŒ ํ›„ pending ๋ณ€๊ฒฝ ์ ์šฉ

๊ฒฐ๊ณผ: โš ๏ธ ํ˜„์žฌ๋Š” ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ (ํ–ฅํ›„ skipNodes๋กœ ๋ณดํ˜ธ ํ•„์š”)

Mermaid ๋‹ค์ด์–ด๊ทธ๋žจ: ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค

๋ถ™์—ฌ๋„ฃ๊ธฐ ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant IH as InputHandler
    participant Editor as Editor
    participant EVD as EditorViewDOM

    User->>Browser: Paste (Ctrl+V)
    Browser->>Browser: DOM ๊ตฌ์กฐ ๋ณ€๊ฒฝ ๊ฐ์ง€
    alt ๋‹จ์ˆœ ํ…์ŠคํŠธ
        Browser->>MO: characterData ๋ณ€๊ฒฝ
        MO->>IH: handleTextContentChange()
        IH->>Editor: executeTransaction()
        Editor->>EVD: editor:content.change (skipRender: true)
        EVD->>EVD: ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ โœ…
    else ๋ณต์žกํ•œ ๊ตฌ์กฐ (HTML, Mark)
        Browser->>MO: childList ๋ณ€๊ฒฝ
        MO->>IH: handleTextContentChange()
        IH->>Editor: executeTransaction()
        Editor->>EVD: editor:content.change (skipRender: false)
        EVD->>EVD: render() ํ˜ธ์ถœ โœ…
        EVD->>Browser: DOM ์—…๋ฐ์ดํŠธ
    end
Loading

Mark ํ† ๊ธ€ ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant EVD as EditorViewDOM
    participant Editor as Editor
    participant Model as Model
    participant DOM as DOM

    User->>EVD: Mod+B (Bold ํ† ๊ธ€)
    EVD->>Editor: toggleMark('bold')
    Editor->>Model: Mark ์ƒํƒœ ๋ณ€๊ฒฝ
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: render() ํ˜ธ์ถœ โœ…
    EVD->>DOM: DOM ์—…๋ฐ์ดํŠธ (Mark ์ ์šฉ)
    EVD->>EVD: Selection ๋ณต์› โœ…
Loading

Undo/Redo ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant EVD as EditorViewDOM
    participant Editor as Editor
    participant History as History
    participant DOM as DOM

    User->>EVD: Mod+Z (Undo)
    EVD->>Editor: undo()
    Editor->>History: ์ด์ „ ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ
    History->>Editor: ์ด์ „ ๋ชจ๋ธ ์ƒํƒœ
    Editor->>Model: ๋ชจ๋ธ ๋ณต์›
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: render() ํ˜ธ์ถœ โœ…
    EVD->>DOM: DOM ์—…๋ฐ์ดํŠธ (์ด์ „ ์ƒํƒœ)
    EVD->>EVD: Selection ๋ณต์› โœ…
Loading

์ž…๋ ฅ ์ค‘ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ์‹œ๋‚˜๋ฆฌ์˜ค (ํ˜„์žฌ vs ํ–ฅํ›„)

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant AI as AI/์™ธ๋ถ€
    participant MO as MutationObserver
    participant Editor as Editor
    participant EVD as EditorViewDOM

    Note over User,EVD: ํ˜„์žฌ ๊ตฌํ˜„
    User->>MO: ์ž…๋ ฅ ์ค‘
    MO->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (skipRender: true)
    AI->>Editor: ์™ธ๋ถ€ ๋ณ€๊ฒฝ
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: render() ํ˜ธ์ถœ โš ๏ธ
    Note over EVD: ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋„ ์—…๋ฐ์ดํŠธ๋จ (์ถฉ๋Œ ๊ฐ€๋Šฅ)
    
    Note over User,EVD: ํ–ฅํ›„ ๊ฐœ์„  (skipNodes)
    User->>MO: ์ž…๋ ฅ ์ค‘
    MO->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (skipRender: true)
    AI->>Editor: ์™ธ๋ถ€ ๋ณ€๊ฒฝ
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: render({ skipNodes: ['nodeA'] }) โœ…
    Note over EVD: ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋Š” ๋ณดํ˜ธ๋จ
Loading

Cross-node Selection ์ž…๋ ฅ ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant IH as InputHandler
    participant Editor as Editor

    User->>Browser: ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection (๋“œ๋ž˜๊ทธ)
    Browser->>Browser: Selection ์ƒ์„ฑ (nodeA + nodeB)
    User->>Browser: ํ…์ŠคํŠธ ์ž…๋ ฅ
    Browser->>Browser: ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์— ์ž…๋ ฅ, Selection ์ถ•์†Œ
    Browser->>MO: characterData ๋ณ€๊ฒฝ (nodeA๋งŒ)
    MO->>IH: handleTextContentChange() (nodeA)
    IH->>Editor: executeTransaction() (nodeA๋งŒ)
    Editor->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (nodeA๋งŒ)
    Note over Editor: nodeB๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
Loading

ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ ํ›„ ํ…์ŠคํŠธ ์ž…๋ ฅ ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant IH as InputHandler
    participant Editor as Editor

    User->>Browser: Home ํ‚ค (๋ผ์ธ ์‹œ์ž‘์œผ๋กœ ์ด๋™)
    Browser->>Browser: Selection ์ด๋™ (์ž…๋ ฅ ์—†์Œ)
    Note over Browser: MutationObserver ๊ฐ์ง€ ์•ˆ ํ•จ
    User->>Browser: ํ…์ŠคํŠธ ์ž…๋ ฅ
    Browser->>Browser: DOM ์—…๋ฐ์ดํŠธ
    Browser->>MO: characterData ๋ณ€๊ฒฝ
    MO->>IH: handleTextContentChange()
    IH->>Editor: executeTransaction()
    Editor->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (skipRender: true)
Loading

๋น„ ํ…์ŠคํŠธ ์š”์†Œ ์‚ฝ์ž… ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant Editor as Editor
    participant EVD as EditorViewDOM

    User->>Browser: ์ด๋ฏธ์ง€ ๋“œ๋กญ (Drag & Drop)
    Browser->>Editor: ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
    Editor->>Editor: ๋ชจ๋ธ์— ์ด๋ฏธ์ง€ ๋…ธ๋“œ ์ถ”๊ฐ€
    Editor->>EVD: editor:content.change (skipRender: false)
    EVD->>EVD: render() ํ˜ธ์ถœ โœ…
    EVD->>Browser: DOM์— ์ด๋ฏธ์ง€ ์š”์†Œ ์ถ”๊ฐ€
    EVD->>EVD: Selection ๋ณต์› โœ…
    
    alt Paste๋กœ ์ด๋ฏธ์ง€ ์‚ฝ์ž…
        User->>Browser: Paste (์ด๋ฏธ์ง€)
        Browser->>Browser: DOM์— ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
        Browser->>MO: childList ๋ณ€๊ฒฝ ๊ฐ์ง€
        MO->>Editor: ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์•Œ๋ฆผ
        Editor->>Editor: ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
        Editor->>EVD: editor:content.change (skipRender: false)
        EVD->>EVD: render() ํ˜ธ์ถœ โœ…
    end
Loading

์ž…๋ ฅ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค

sequenceDiagram
    participant User as ์‚ฌ์šฉ์ž
    participant Browser as ๋ธŒ๋ผ์šฐ์ €
    participant MO as MutationObserver
    participant IH as InputHandler
    participant Editor as Editor
    participant Network as ๋„คํŠธ์›Œํฌ

    User->>Browser: ์ž…๋ ฅ ์ค‘
    Browser->>MO: characterData ๋ณ€๊ฒฝ
    MO->>IH: handleTextContentChange()
    IH->>Editor: executeTransaction()
    Editor->>Network: ๋™๊ธฐํ™” ์‹œ๋„
    Network-->>Editor: ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ โŒ
    Editor->>Editor: ๋กœ์ปฌ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ (์„ฑ๊ณต)
    Editor->>Editor: editor:content.change (skipRender: true)
    Note over Editor: ๋กœ์ปฌ ์ž…๋ ฅ์€ ์ •์ƒ ์ฒ˜๋ฆฌ
    Note over Editor: ๋™๊ธฐํ™”๋Š” ์žฌ์‹œ๋„ ํ์— ์ถ”๊ฐ€
Loading

ํ•ต์‹ฌ ์›์น™

1. ์ž…๋ ฅ ์ค‘์—๋Š” ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ

์ด์œ :

  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์ง์ ‘ ์—…๋ฐ์ดํŠธํ•จ (contentEditable)
  • ์ถ”๊ฐ€ ๋ Œ๋”๋ง์€ ๋ถˆํ•„์š”ํ•˜๊ณ  Selection๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์Œ
  • ๋ชจ๋ธ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ์ถฉ๋ถ„ํ•จ

๊ตฌํ˜„:

  • skipRender: true๋กœ ์„ค์ •
  • editor:content.change ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒดํฌํ•˜์—ฌ ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€

2. ์™ธ๋ถ€ ๋ณ€๊ฒฝ๋งŒ ๋ Œ๋”๋ง

์ด์œ :

  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ ๋ชจ๋ธ๋งŒ ๋ณ€๊ฒฝ๋˜๊ณ  DOM์€ ์•„์ง ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Œ
  • ๋ Œ๋”๋ง์ด ํ•„์š”ํ•จ

๊ตฌํ˜„:

  • skipRender: false (๋˜๋Š” undefined)
  • editor:content.change ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋ Œ๋”๋ง ์ˆ˜ํ–‰

3. ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง ์—†์Œ

์ด์œ :

  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์—…๋ฐ์ดํŠธํ–ˆ์Œ
  • ์žฌ๋ Œ๋”๋งํ•˜๋ฉด Selection๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์Œ
  • ๋ชจ๋ธ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ์ด๋ฏธ ๋ฐ˜์˜๋˜์–ด ์žˆ์Œ

๊ตฌํ˜„:

  • _onInputEnd()์—์„œ ์žฌ๋ Œ๋”๋ง ์ œ๊ฑฐ
  • _editingNodes๋งŒ ์ดˆ๊ธฐํ™”

ํƒ€์ž„๋ผ์ธ ๋‹ค์ด์–ด๊ทธ๋žจ

Mermaid Gantt: ์ž…๋ ฅ ์ค‘ (์ •์ƒ ๋™์ž‘)

gantt
    title ์ž…๋ ฅ ์ค‘ ํƒ€์ž„๋ผ์ธ (์ •์ƒ ๋™์ž‘)
    dateFormat X
    axisFormat %Ls
    
    section ๋ธŒ๋ผ์šฐ์ €
    DOM ์—…๋ฐ์ดํŠธ + Selection ์œ ์ง€ :0, 10
    
    section MutationObserver
    ๋ณ€๊ฒฝ ๊ฐ์ง€ :10, 5
    
    section InputHandler
    ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ :15, 10
    
    section EditorViewDOM
    skipRender ์ฒดํฌ :25, 5
    ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ :30, 0
Loading

Mermaid Gantt: ์™ธ๋ถ€ ๋ณ€๊ฒฝ (์ •์ƒ ๋™์ž‘)

gantt
    title ์™ธ๋ถ€ ๋ณ€๊ฒฝ ํƒ€์ž„๋ผ์ธ (์ •์ƒ ๋™์ž‘)
    dateFormat X
    axisFormat %Ls
    
    section Editor
    ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ :0, 10
    
    section EditorViewDOM
    skipRender ์ฒดํฌ :10, 5
    render() ํ˜ธ์ถœ :15, 20
    
    section DOM
    DOM ์—…๋ฐ์ดํŠธ :20, 10
    Selection ๋ณต์› :30, 5
Loading

Mermaid Gantt: Race Condition ๋ฐœ์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค (ํ•ด๊ฒฐ ์ „)

gantt
    title Race Condition ๋ฐœ์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค (ํ•ด๊ฒฐ ์ „)
    dateFormat X
    axisFormat %Ls
    
    section ๋ธŒ๋ผ์šฐ์ €
    DOM ์—…๋ฐ์ดํŠธ :0, 10
    
    section MutationObserver
    ๋ณ€๊ฒฝ ๊ฐ์ง€ :10, 5
    
    section InputHandler
    ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ :15, 10
    
    section EditorViewDOM
    render() ํ˜ธ์ถœ (๋™์‹œ) :20, 20
    
    section Selection
    Selection ๋ณ€๊ฒฝ (๋™์‹œ) :20, 15
    
    section ์ถฉ๋Œ
    โŒ ์ถฉ๋Œ ๋ฐœ์ƒ :25, 10
Loading

Mermaid Gantt: Race Condition ํ•ด๊ฒฐ ํ›„

gantt
    title Race Condition ํ•ด๊ฒฐ ํ›„
    dateFormat X
    axisFormat %Ls
    
    section ๋ธŒ๋ผ์šฐ์ €
    DOM ์—…๋ฐ์ดํŠธ + Selection ์œ ์ง€ :0, 10
    
    section MutationObserver
    ๋ณ€๊ฒฝ ๊ฐ์ง€ :10, 5
    
    section InputHandler
    ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ :15, 10
    
    section EditorViewDOM
    skipRender ์ฒดํฌ :25, 5
    โœ… ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ :30, 0
    
    section Selection
    โœ… ๋ณ€๊ฒฝ ์—†์Œ (๋ธŒ๋ผ์šฐ์ € ์œ ์ง€) :0, 35
Loading

๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ

1. InputHandler์—์„œ skipRender ์„ค์ •

ํŒŒ์ผ: packages/editor-view-dom/src/event-handlers/input-handler.ts

// commitPendingImmediate()์—์„œ
console.log('[Input] commitPendingImmediate: emit editor:content.change (skipRender=true, from=MutationObserver)', { nodeId });
this.editor.emit('editor:content.change', {
  skipRender: true, // ํ•„์ˆ˜: MutationObserver ๋ณ€๊ฒฝ์€ render() ํ˜ธ์ถœ ์•ˆ ํ•จ
  from: 'MutationObserver',
  transaction: {
    type: 'text_replace',
    nodeId: textNodeId
  }
});

2. EditorViewDOM์—์„œ skipRender ์ฒดํฌ

ํŒŒ์ผ: packages/editor-view-dom/src/editor-view-dom.ts

this.editor.on('editor:content.change', (e: any) => {
  // ๋ Œ๋”๋ง ์ค‘์ด๋ฉด ๋ฌด์‹œ (๋ฌดํ•œ๋ฃจํ”„ ๋ฐฉ์ง€)
  if (this._isRendering) {
    return;
  }
  
  // skipRender: true์ธ ๊ฒฝ์šฐ ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ
  if (e?.skipRender) {
    console.log('[EditorViewDOM] content.change: SKIP (skipRender=true)', {
      from: e?.from || 'unknown',
      transactionType: e?.transaction?.type,
      nodeId: e?.transaction?.nodeId
    });
    return;
  }
  
  // ์™ธ๋ถ€ ๋ณ€๊ฒฝ๋งŒ ๋ Œ๋”๋ง
  this.render();
});

3. ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง ์ œ๊ฑฐ

ํŒŒ์ผ: packages/editor-view-dom/src/editor-view-dom.ts

private _onInputEnd(): void {
  this._inputEndDebounceTimer = window.setTimeout(() => {
    // editingNodes ์ดˆ๊ธฐํ™”๋งŒ ์ˆ˜ํ–‰
    this._editingNodes.clear();
    console.log('[EditorViewDOM] Input ended, editingNodes cleared');
    // ์žฌ๋ Œ๋”๋ง์€ ํ•˜์ง€ ์•Š์Œ
  }, 500);
}

๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง ์ฐจ๋‹จ

  • skipRender: true ์„ค์ •
  • editor:content.change ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒดํฌ
  • ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€ ํ™•์ธ

โœ… Selection ๋ณด์กด

  • ์ž…๋ ฅ ์ค‘์—๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ Selection ์œ ์ง€
  • ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ Selection ๋ณ€๊ฒฝ ์—†์Œ
  • Race condition ์—†์Œ

โœ… ์™ธ๋ถ€ ๋ณ€๊ฒฝ ์ฒ˜๋ฆฌ

  • skipRender: false์ธ ๊ฒฝ์šฐ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  • DOM์ด ๋ชจ๋ธ ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธ
  • Selection ๋ณต์›

โœ… ์ž…๋ ฅ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ

  • ์žฌ๋ Œ๋”๋ง ์—†์Œ
  • _editingNodes ์ดˆ๊ธฐํ™”
  • ์ถฉ๋Œ ์—†์Œ

์ฃผ์˜์‚ฌํ•ญ

1. skipRender๋Š” MutationObserver ๋ณ€๊ฒฝ์—๋งŒ ์ ์šฉ

์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ:

  • โœ… MutationObserver์—์„œ ๊ฐ์ง€ํ•œ characterData ๋ณ€๊ฒฝ: skipRender: true
  • โœ… ์™ธ๋ถ€ ๋ณ€๊ฒฝ (model-change): skipRender: false (๋˜๋Š” undefined)

์ž˜๋ชป๋œ ์‚ฌ์šฉ:

  • โŒ ๋ชจ๋“  ๋ณ€๊ฒฝ์— skipRender: true: ์™ธ๋ถ€ ๋ณ€๊ฒฝ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š์Œ
  • โŒ ๋ชจ๋“  ๋ณ€๊ฒฝ์— skipRender: false: ์ž…๋ ฅ ์ค‘ race condition ๋ฐœ์ƒ

2. ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง ๋ถˆํ•„์š”

์ด์œ :

  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์—…๋ฐ์ดํŠธํ–ˆ์Œ
  • ๋ชจ๋ธ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ์ด๋ฏธ ๋ฐ˜์˜๋˜์–ด ์žˆ์Œ
  • ์žฌ๋ Œ๋”๋งํ•˜๋ฉด Selection๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์Œ

3. IME ์กฐํ•ฉ ์ค‘ ์ฒ˜๋ฆฌ

ํ˜„์žฌ ๊ตฌํ˜„:

  • IME ์กฐํ•ฉ ์ค‘์—๋„ skipRender: true๋กœ ์ฒ˜๋ฆฌ
  • ์กฐํ•ฉ ์™„๋ฃŒ ํ›„ commitPendingImmediate() ํ˜ธ์ถœ
  • ์กฐํ•ฉ ์™„๋ฃŒ ํ›„์—๋„ skipRender: true ์œ ์ง€

4. Cross-node Selection ์ฒ˜๋ฆฌ

ํŠน์ง•:

  • ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ
  • ์ž…๋ ฅ์€ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์—๋งŒ ์ ์šฉ๋จ
  • Selection์€ ์ž…๋ ฅ ํ›„ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์˜ ์ž…๋ ฅ ์œ„์น˜๋กœ ์ถ•์†Œ

์ฃผ์˜:

  • ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ Selection ํ›„ ์ž…๋ ฅ ์‹œ, ๋‚˜๋จธ์ง€ ๋…ธ๋“œ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
  • ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„ํ•œ ๋™์ž‘๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ (ํ–ฅํ›„ ๊ฐœ์„  ๊ฐ€๋Šฅ)

5. ๋น„ ํ…์ŠคํŠธ ์š”์†Œ ์‚ฝ์ž…

ํŠน์ง•:

  • ์ด๋ฏธ์ง€๋‚˜ Embed ์‚ฝ์ž…์€ DOM ๊ตฌ์กฐ ๋ณ€๊ฒฝ์„ ์ผ์œผํ‚ด
  • skipRender: false๋กœ ๋ Œ๋”๋ง ์ˆ˜ํ–‰
  • Selection์€ ์‚ฝ์ž…๋œ ์š”์†Œ ๋‹ค์Œ์œผ๋กœ ์ด๋™

์ฃผ์˜:

  • Drag & Drop๊ณผ Paste์˜ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ
  • ๋ณต์žกํ•œ ๊ตฌ์กฐ์˜ ๋ถ™์—ฌ๋„ฃ๊ธฐ๋Š” ์ถ”๊ฐ€ ๊ฒ€์ฆ ํ•„์š”

6. ์ž…๋ ฅ ์ค‘ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

์›์น™:

  • ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ์—๋„ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์€ ๋ณดํ˜ธ๋˜์–ด์•ผ ํ•จ
  • ๋กœ์ปฌ ์ž…๋ ฅ์€ ์ •์ƒ ์ฒ˜๋ฆฌ, ์—๋Ÿฌ๋Š” ๋ณ„๋„ ์ฒ˜๋ฆฌ
  • ์‚ฌ์šฉ์ž๋Š” ์ž…๋ ฅ์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ

๊ตฌํ˜„:

  • ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ: ๋กœ์ปฌ ์ž…๋ ฅ์€ ์ฒ˜๋ฆฌ, ๋™๊ธฐํ™”๋Š” ์žฌ์‹œ๋„
  • ๋ชจ๋ธ ๊ฒ€์ฆ ์‹คํŒจ: DOM ์ž…๋ ฅ์€ ์œ ์ง€, ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ๋งŒ ์‹คํŒจ
  • ๋ Œ๋”๋ง ์—๋Ÿฌ: ์ž…๋ ฅ์€ ์ •์ƒ, ์—๋Ÿฌ๋Š” ๋ณ„๋„ ๋กœ๊น…

7. ์ž…๋ ฅ ์ค‘ ๋™์ผ ๋…ธ๋“œ ์™ธ๋ถ€ ๋ณ€๊ฒฝ

ํ˜„์žฌ ๋ฌธ์ œ:

  • ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋„ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ์‹œ ์—…๋ฐ์ดํŠธ๋จ
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฎ์–ด์”Œ์›Œ์งˆ ์ˆ˜ ์žˆ์Œ

ํ–ฅํ›„ ๊ฐœ์„ :

  • skipNodes ์˜ต์…˜์œผ๋กœ ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ ๋ณดํ˜ธ
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ pending์— ์ €์žฅ
  • ์ž…๋ ฅ ์™„๋ฃŒ ํ›„ pending ๋ณ€๊ฒฝ ์ ์šฉ

๊ด€๋ จ ๋ฌธ์„œ

  • TEXT_INPUT_DATA_FLOW.md - ํ…์ŠคํŠธ ์ž…๋ ฅ ์‹œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ํ๋ฆ„
  • TEXT_INPUT_FLOW.md - EditorViewDOM ๊ธ€์ž ์ž…๋ ฅ ๋ฐ˜์‘ ํ๋ฆ„
  • protecting-user-input-from-external-changes.md - ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ณดํ˜ธ (AI/๋™์‹œํŽธ์ง‘)

์š”์•ฝ

ํ•ด๊ฒฐ๋œ ๋ฌธ์ œ

  1. ์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง๊ณผ Selection ๋ณ€๊ฒฝ์˜ Race Condition

    • ์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ (skipRender: true)
    • Selection์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์œ ์ง€
    • ์ถฉ๋Œ ์—†์ด ๋ถ€๋“œ๋Ÿฌ์šด ์ž…๋ ฅ ๊ฒฝํ—˜
  2. ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ๋ถˆํ•„์š”ํ•œ ์žฌ๋ Œ๋”๋ง

    • ์ž…๋ ฅ ์ข…๋ฃŒ ์‹œ ์žฌ๋ Œ๋”๋ง ์ œ๊ฑฐ
    • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ DOM์„ ์—…๋ฐ์ดํŠธํ–ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ๋ Œ๋”๋ง ๋ถˆํ•„์š”
    • Selection๊ณผ์˜ ์ถฉ๋Œ ๋ฐฉ์ง€

ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜

  1. skipRender ์˜ต์…˜

    • MutationObserver ๋ณ€๊ฒฝ: skipRender: true (๋ Œ๋”๋ง ์ฐจ๋‹จ)
    • ์™ธ๋ถ€ ๋ณ€๊ฒฝ: skipRender: false (๋ Œ๋”๋ง ์ˆ˜ํ–‰)
  2. ๋ธŒ๋ผ์šฐ์ € ์˜์กด

    • ์ž…๋ ฅ ์ค‘ DOM ์—…๋ฐ์ดํŠธ๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌ
    • Selection ์œ ์ง€๋„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ
    • ์šฐ๋ฆฌ๋Š” ๋ชจ๋ธ๋งŒ ์—…๋ฐ์ดํŠธ
  3. ์ด๋ฒคํŠธ ์†Œ์Šค ๊ตฌ๋ถ„

    • from: 'MutationObserver' โ†’ skipRender: true
    • from: 'model-change' โ†’ skipRender: false

์ง€์›๋˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค

๊ธฐ๋ณธ ์ž…๋ ฅ

  • โœ… ๊ธฐ๋ณธ ์ž…๋ ฅ (characterData)
  • โœ… Selection ์ด๋™ ์ค‘ ์ž…๋ ฅ (Shift+Arrow)
  • โœ… ๋ฐฑ์ŠคํŽ˜์ด์Šค/Delete ํ‚ค ์ž…๋ ฅ
  • โœ… ํŠน์ˆ˜ ํ‚ค ์ž…๋ ฅ (Home/End, Ctrl+Arrow ๋“ฑ)

IME ์ž…๋ ฅ

  • โœ… IME ์กฐํ•ฉ (ํ•œ๊ตญ์–ด, ์ผ๋ณธ์–ด, ์ค‘๊ตญ์–ด)
  • โœ… IME ์กฐํ•ฉ ์™„๋ฃŒ

์™ธ๋ถ€ ๋ณ€๊ฒฝ

  • โœ… ์™ธ๋ถ€ ๋ณ€๊ฒฝ (model-change)
  • โœ… ์™ธ๋ถ€ Decorator ๋ณ€๊ฒฝ (๋Œ“๊ธ€, AI ๊ฐ•์กฐ ๋“ฑ)
  • โœ… ์™ธ๋ถ€ Selection ๋™๊ธฐํ™” (ํ˜‘์—… ์‚ฌ์šฉ์ž)

๋ณต์‚ฌ/๋ถ™์—ฌ๋„ฃ๊ธฐ

  • โœ… ๋ถ™์—ฌ๋„ฃ๊ธฐ (๋‹จ์ˆœ ํ…์ŠคํŠธ ๋ฐ ๋ณต์žกํ•œ ๊ตฌ์กฐ)
  • โœ… ๋ณต์‚ฌ (copy)

์„ ํƒ ๋ฐ ์ž…๋ ฅ

  • โœ… ๋“œ๋ž˜๊ทธ ์„ ํƒ ํ›„ ์ž…๋ ฅ
  • โœ… Range ์„ ํƒ ํ›„ ์ž…๋ ฅ (ํ…์ŠคํŠธ ๊ต์ฒด)
  • โœ… Cross-node Selection ์ž…๋ ฅ (์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๊ฑธ์นœ ์„ ํƒ)

Mark ํ† ๊ธ€

  • โœ… Mark ํ† ๊ธ€ (Bold, Italic ๋“ฑ)
  • โœ… Mark ํ† ๊ธ€ ์ค‘ ์ž…๋ ฅ

Undo/Redo

  • โœ… Undo (Mod+Z)
  • โœ… Redo (Mod+Shift+Z)
  • โœ… Undo ์ค‘ ์ž…๋ ฅ

๋‹ค์ค‘ ์ž…๋ ฅ

  • โœ… ์—ฌ๋Ÿฌ ๋…ธ๋“œ ๋™์‹œ ์ž…๋ ฅ
  • โœ… ์ž…๋ ฅ ์ค‘ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ํฌ์ปค์Šค ์ด๋™

๋น„ ํ…์ŠคํŠธ ์š”์†Œ

  • โœ… ์ด๋ฏธ์ง€ ์‚ฝ์ž… (drag & drop, paste)
  • โœ… Embed ์š”์†Œ ์‚ฝ์ž…

์Šคํฌ๋กค ๋ฐ ๋ ˆ์ด์•„์›ƒ

  • โœ… ์ž…๋ ฅ ์ค‘ ์Šคํฌ๋กค ๋ฐœ์ƒ
  • โœ… ์ž…๋ ฅ ์ค‘ ์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ
  • โœ… ์ž…๋ ฅ ์ค‘ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ํŠธ๋ฆฌ๊ฑฐ

์—๋Ÿฌ ๋ฐ ์˜ˆ์™ธ

  • โœ… ์ž…๋ ฅ ์ค‘ ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ (ํ˜‘์—…)
  • โœ… ์ž…๋ ฅ ์ค‘ ๋ชจ๋ธ ๊ฒ€์ฆ ์‹คํŒจ
  • โœ… ์ž…๋ ฅ ์ค‘ ๋ Œ๋”๋ง ์—๋Ÿฌ

ํŠน์ˆ˜ ์ผ€์ด์Šค

  • โœ… ์ž…๋ ฅ ์ข…๋ฃŒ
  • โœ… ์ž…๋ ฅ ์ค‘ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ๊ฐ์ง€ (๋‹ค๋ฅธ ๋…ธ๋“œ)
  • โœ… ๋ Œ๋”๋ง ์ค‘ ์ž…๋ ฅ ๊ฐ์ง€
  • โš ๏ธ ์ž…๋ ฅ ์ค‘ ๋™์ผ ๋…ธ๋“œ ์™ธ๋ถ€ ๋ณ€๊ฒฝ (ํ–ฅํ›„ skipNodes๋กœ ๋ณดํ˜ธ ํ•„์š”)

ํ–ฅํ›„ ๊ฐœ์„  ์‚ฌํ•ญ

โš ๏ธ ์ž…๋ ฅ ์ค‘ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ๋ณดํ˜ธ

  • ํ˜„์žฌ: ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ๋„ ์™ธ๋ถ€ ๋ณ€๊ฒฝ ์‹œ ์—…๋ฐ์ดํŠธ๋จ
  • ํ–ฅํ›„: skipNodes ์˜ต์…˜์œผ๋กœ ์ž…๋ ฅ ์ค‘์ธ ๋…ธ๋“œ ๋ณดํ˜ธ
  • ์™ธ๋ถ€ ๋ณ€๊ฒฝ์€ pending์— ์ €์žฅ ํ›„ ์ž…๋ ฅ ์™„๋ฃŒ ์‹œ ์ ์šฉ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment