Search
๐Ÿฆ‰

Prosemirror Guide2: Documents, Transformation, Commands

subtitle
๊ณต์‹ ๋ฌธ์„œ ๋ฒˆ์—ญ ๋ฐ ๋‚ด์šฉ ์ถ”๊ฐ€
Tags
์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ
front-end
Created
2025/08/04
2 more properties

My first editor

์ €๋ฒˆ ๊ธ€์—์„œ๋Š” Prosemirror ์—๋””ํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์•Œ์•„์•ผ ํ•˜๋Š” ์„ธ ๊ฐ€์ง€(schema, state, view)๋ฅผ ์‚ดํŽด๋ดค๋‹ค. ์ด๋กœ์จ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ์—๋””ํ„ฐ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฐœ๋…์„ ์ตํ˜”๋‹ค.
import {schema} from "prosemirror-schema-basic" import {EditorState} from "prosemirror-state" import {EditorView} from "prosemirror-view" let state = EditorState.create({schema}) let view = new EditorView(document.body, {state})
TypeScript
๋ณต์‚ฌ
์œ„ ์˜ˆ์‹œ๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ์—๋””ํ„ฐ ์˜ˆ์‹œ์ด๋‹ค. ProseMirror๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ฌธ์„œ๊ฐ€ ๋”ฐ๋ผ์•ผ ํ•  ์Šคํ‚ค๋งˆ(schema) ๋ฅผ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ์€, ๊ธฐ๋ณธ ์Šคํ‚ค๋งˆ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“ˆ์„ import ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ ๋‹ค์Œ, ์ด ์Šคํ‚ค๋งˆ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์—๋””ํ„ฐ ์ƒํƒœ(state) ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. state๋Š” ์Šคํ‚ค๋งˆ์— ๋งž๋Š” ๋นˆ ๋ฌธ์„œ(document) ๋ฅผ ๋งŒ๋“ค๊ณ , ๋ฌธ์„œ์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ๊ธฐ๋ณธ ์„ ํƒ ์˜์—ญ(selection) ์„ ์„ค์ •ํ•œ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์ด ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์—๋””ํ„ฐ ๋ทฐ(view) ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ document.body์— ๋ถ™์ธ๋‹ค.
์ด๋กœ์จ document๋Š” ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ DOM ๋…ธ๋“œ๋กœ ๋ Œ๋”๋ง๋˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํŠธ๋žœ์žญ์…˜(transaction) ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
์ž ๊น, ํŠธ๋žœ์žญ์…˜? ์ด๊ฑด ๋˜ ๋ญ˜๊นŒ? ์ด ๊ฐ„๋‹จํ•œ ์—๋””ํ„ฐ๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๊ณ  ํ™•์žฅํ•˜๊ธฐ ์œ„ํ•ด Transaction, Plugins, Commands, Content ๊ฐœ๋…์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•œ๋‹ค.

Transaction

์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ์—๋””ํ„ฐ ๋ทฐ์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ๋•Œ, ProseMirror๋Š” ์ด๋ฅผ ์ƒํƒœ ํŠธ๋žœ์žญ์…˜(state transaction)์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด ๋ง์€, ๋‹จ์ˆœํžˆ ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๊ณ  ๊ทธ๊ฑธ๋กœ ์ƒํƒœ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ”๋€Œ๋Š” ์‹์ด ์•„๋‹ˆ๋ผ๋Š” ๋œป์ด๋‹ค.
๋Œ€์‹ , ์–ด๋–ค ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋ฉด ProseMirror๋Š” ํŠธ๋žœ์žญ์…˜(transaction) ์„ ์ƒ์„ฑํ•œ๋‹ค. ์ด ํŠธ๋žœ์žญ์…˜์€ ๋ฌด์—‡์ด ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๊ฐ์ฒด๋กœ, ์ด๋ฅผ ๊ธฐ์กด ์ƒํƒœ์— ์ ์šฉํ•˜๋ฉด ์ƒˆ๋กœ์šด ์ƒํƒœ(state) ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์ด ์ƒˆ๋กœ์šด ์ƒํƒœ๋Š” ๋‹ค์‹œ ๋ทฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์ด์ „ ๊ธ€์—์„œ๋Š” view component๋ฅผ ๋‹ค๋ฃฐ ๋•Œ view โ†’ (DOM event) -> (transaction) โ†’ state โ†’ view ์ด๋ผ๋Š” ๋ฐ์ดํ„ฐ์˜ ์ˆœํ™˜์œผ๋กœ ์„ค๋ช…ํ–ˆ์—ˆ๋‹ค.
์ด๋Ÿฌํ•œ ํ๋ฆ„์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด๋ถ€์—์„œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์ง€๋งŒ, ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ๋ทฐ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ง์ ‘ ๊ฐœ์ž…ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜ ์ฝ”๋“œ๋Š” dispatchTransaction์ด๋ผ๋Š” prop์„ ์ง€์ •ํ•ด์„œ ํŠธ๋žœ์žญ์…˜์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ๋กœ์ง์„ ์ง์ ‘ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค.
let state = EditorState.create({ schema }) let view = new EditorView(document.body, { state, dispatchTransaction(transaction) { console.log("Document size went from", transaction.before.content.size, "to", transaction.doc.content.size) let newState = view.state.apply(transaction) view.updateState(newState) } })
JavaScript
๋ณต์‚ฌ
์—ฌ๊ธฐ์„œ dispatchTransaction์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค.
1.
ํŠธ๋žœ์žญ์…˜์ด ๋ฐœ์ƒํ•˜๋ฉด, ๋ณ€๊ฒฝ ์ „๊ณผ ํ›„์˜ ๋ฌธ์„œ ํฌ๊ธฐ๋ฅผ ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜๊ณ 
2.
ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉํ•ด ์ƒˆ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค๊ณ 
3.
view.updateState()๋ฅผ ํ˜ธ์ถœํ•ด ์—๋””ํ„ฐ๋ฅผ ์ƒˆ ์ƒํƒœ๋กœ ๊ฐฑ์‹ ํ•œ๋‹ค
ProseMirror์—์„œ๋Š” ๋ชจ๋“  ์ƒํƒœ ๊ฐฑ์‹ ์ด ๋ฐ˜๋“œ์‹œ updateState()๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ ธ์•ผ ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์ธ ํŽธ์ง‘ ์ž‘์—…์€ ๋ชจ๋‘ ํŠธ๋žœ์žญ์…˜์„ ๋””์ŠคํŒจ์น˜(dispatch) ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค. ์ฆ‰, ProseMirror๋Š” ์‚ฌ์šฉ์ž ์กฐ์ž‘ โ†’ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ โ†’ ์ƒํƒœ ์ ์šฉ โ†’ ๋ทฐ ๊ฐฑ์‹ ์ด๋ผ๋Š” ๋ช…ํ™•ํ•œ ํ๋ฆ„์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ํ•„์š”ํ•˜๋‹ค๋ฉด ๊ทธ ํ๋ฆ„์˜ ์ค‘๊ฐ„์„ ์ง์ ‘ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ–์ถ”๊ณ  ์žˆ๋‹ค.

Plugins

ํ”Œ๋Ÿฌ๊ทธ์ธ(Plugins)์€ ์—๋””ํ„ฐ์™€ ์—๋””ํ„ฐ state์˜ ๋™์ž‘์„ ํ™•์žฅํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์–ด๋–ค ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋‹จ์ˆœํ•˜๊ฒŒ ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์— ๋™์ž‘์„ ์—ฐ๊ฒฐํ•˜๋Š” keymap ํ”Œ๋Ÿฌ๊ทธ์ธ์ฒ˜๋Ÿผ ๋น„๊ต์  ๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ์„ ํ•˜๊ธฐ๋„ ํ•˜๊ณ , history ํ”Œ๋Ÿฌ๊ทธ์ธ์ฒ˜๋Ÿผ ๋” ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ํ•˜๊ธฐ๋„ ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด history ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ํŠธ๋žœ์žญ์…˜์„ ๊ด€์ฐฐํ•ด์„œ ๋˜๋Œ๋ฆฌ๊ธฐ(undo)๋ฅผ ์œ„ํ•œ ๋ฐ˜๋Œ€ ์ž‘์—…์„ ์ €์žฅํ•ด๋‘”๋‹ค.
์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ด ๋‘ ๊ฐ€์ง€ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์—๋””ํ„ฐ์— ์ถ”๊ฐ€ํ•ด, ๋˜๋Œ๋ฆฌ๊ธฐ/๋‹ค์‹œ ์‹คํ–‰(undo/redo) ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค.
import { undo, redo, history } from "prosemirror-history" import { keymap } from "prosemirror-keymap" let state = EditorState.create({ schema, plugins: [ history(), keymap({ "Mod-z": undo, "Mod-y": redo }) // Ctrl/Cmd+Z โ†’ undo, Ctrl/Cmd+Y โ†’ redo ] }) let view = new EditorView(document.body, { state })
JavaScript
๋ณต์‚ฌ
์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ํฌ์ธํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์—๋””ํ„ฐ state๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํ•จ๊ป˜ ๋“ฑ๋ก๋˜์–ด์•ผ ํ•œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์ƒํƒœ ํŠธ๋žœ์žญ์…˜์— ์ ‘๊ทผํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋ ‡๊ฒŒ history ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋œ ์ƒํƒœ๋กœ ์—๋””ํ„ฐ ๋ทฐ๋ฅผ ๋งŒ๋“ค๋ฉด, ์ด์ œ ์‚ฌ์šฉ์ž๊ฐ€ Ctrl+Z (๋˜๋Š” Mac์—์„œ๋Š” Cmd+Z) ๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ๋งˆ์ง€๋ง‰ ๋ณ€๊ฒฝ์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
์ฆ‰, ์ด ์˜ˆ์‹œ์ฒ˜๋Ÿผ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํ†ตํ•ด ์—๋””ํ„ฐ์˜ ๊ธฐ๋Šฅ์„ ์œ ์—ฐํ•˜๊ฒŒ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๊ณ , ๋˜๋Œ๋ฆฌ๊ธฐ/๋‹ค์‹œ ์‹คํ–‰ ๊ฐ™์€ ํŽธ์ง‘๊ธฐ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๋„ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์ง์ ‘ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š”, ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์— ์šฉ์ดํ•œ ๊ตฌ์กฐ๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

Commands

์ด์ „ ์˜ˆ์‹œ์—์„œ ํ‚ค์— ๋ฐ”์ธ๋”ฉํ–ˆ๋˜ undo ์™€ redo ๋Š” commands(๋ช…๋ น์–ด) ๋ผ ๋ถˆ๋ฆฌ๋Š” ํŠน๋ณ„ํ•œ ์ข…๋ฅ˜์˜ ํ•จ์ˆ˜๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ํŽธ์ง‘ ์ž‘์—…(์˜ˆ: ์ค„๋ฐ”๊ฟˆ, ์‚ญ์ œ, ๋˜๋Œ๋ฆฌ๊ธฐ ๋“ฑ)์€ ์ด command ํ˜•ํƒœ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ๊ณ , ์ด๋“ค์€ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค์— ๋ฐ”์ธ๋”ฉํ•˜๊ฑฐ๋‚˜ ๋ฉ”๋‰ด์— ์—ฐ๊ฒฐํ•˜๊ฑฐ๋‚˜, ๊ธฐํƒ€ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์— ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
prosemirror-commands ํŒจํ‚ค์ง€๋Š” ์ด๋Ÿฐ command ๋“ค ์ค‘์—์„œ ๊ธฐ๋ณธ์ ์ธ ํŽธ์ง‘ ๋ช…๋ น์–ด๋“ค์„ ๋‹ค์ˆ˜ ์ œ๊ณตํ•˜๋ฉฐ, baseKeymap์ด๋ผ๋Š” ์ตœ์†Œํ•œ์˜ ํ‚ค๋งต๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•œ๋‹ค. ์ด ํ‚ค๋งต์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด Enter๋‚˜ Delete ํ‚ค๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ๊ธฐ๋Œ€ํ•˜๋Š” ๋™์ž‘์ด ์ˆ˜ํ–‰๋˜๋„๋ก ํ•ด์ค€๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜ ์ฝ”๋“œ๋Š” editor์— ๊ธฐ๋ณธ์ ์ธ ํ‚ค ๋™์ž‘์„ ํฌํ•จํ•œ ๋ช…๋ น์–ด๋“ค์„ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
import { baseKeymap } from "prosemirror-commands" let state = EditorState.create({ schema, plugins: [ history(), keymap({ "Mod-z": undo, "Mod-y": redo }), // ๋˜๋Œ๋ฆฌ๊ธฐ/๋‹ค์‹œ ์‹คํ–‰ ๋‹จ์ถ•ํ‚ค keymap(baseKeymap) // Enter, Delete ๋“ฑ ๊ธฐ๋ณธ ๋ช…๋ น์–ด ๋ฐ”์ธ๋”ฉ ] }) let view = new EditorView(document.body, { state })
JavaScript
๋ณต์‚ฌ
์—ฌ๊ธฐ๊นŒ์ง€ ์„ค์ •ํ•˜๋ฉด, ๊ธฐ๋ณธ์ ์ธ ํŽธ์ง‘ ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•˜๋Š” ์‹ค์ œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์—๋””ํ„ฐ๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค.
๋งŒ์•ฝ ๋ฉ”๋‰ด, ์ปค์Šคํ…€ ๋‹จ์ถ•ํ‚ค, ์Šคํ‚ค๋งˆ์— ํŠนํ™”๋œ ๋™์ž‘๋“ค์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด prosemirror-example-setup ํŒจํ‚ค์ง€๋ฅผ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ชจ๋“ˆ์€ ์—๋””ํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชจ์Œ์„ ์ œ๊ณตํ•œ๋‹ค. ๋‹จ, ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ์ด๋Š” ์‹ค์ œ ์„œ๋น„์Šค์šฉ๋ณด๋‹ค๋Š” ํ•™์Šต์šฉ/์˜ˆ์ œ์šฉ ๊ตฌ์„ฑ์„ ๋ชฉ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์—, ์‹ค์ œ๋กœ ๋ฐฐํฌํ•  ์„œ๋น„์Šค์—์„œ๋Š” ์ด ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ์“ฐ๊ธฐ๋ณด๋‹ค๋Š” ์›ํ•˜๋Š” ๋Œ€๋กœ ์„ค์ •ํ•œ ์ปค์Šคํ…€ ์ฝ”๋“œ๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค.

Content

์—๋””ํ„ฐ ์ƒํƒœ(EditorState)์˜ ๋ฌธ์„œ๋Š” doc ์†์„ฑ ์•„๋ž˜์— ์กด์žฌํ•œ๋‹ค. ์ด doc์€ ์ฝ๊ธฐ ์ „์šฉ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ด๋ฉฐ, ๋…ธ๋“œ๋“ค์˜ ๊ณ„์ธต ๊ตฌ์กฐ๋กœ ๋ฌธ์„œ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค. ์ด ๊ตฌ์กฐ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ DOM๊ณผ ๋น„์Šทํ•˜๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ„๋‹จํ•œ ๋ฌธ์„œ๋Š” doc ๋…ธ๋“œ ์•ˆ์— ๋‘ ๊ฐœ์˜ paragraph(๋ฌธ๋‹จ) ๋…ธ๋“œ๊ฐ€ ์žˆ๊ณ , ๊ฐ ๋ฌธ๋‹จ ์•ˆ์— ํ•˜๋‚˜์˜ text ๋…ธ๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๋Š” ํ˜•ํƒœ์ผ ์ˆ˜ ์žˆ๋‹ค. ์—๋””ํ„ฐ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ, ์ดˆ๊ธฐ ๋ฌธ์„œ(doc)๋ฅผ ์ง์ ‘ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” schema ํ•„๋“œ๋ฅผ ์ƒ๋žตํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ๋ฌธ์„œ ๊ฐ์ฒด(doc) ์•ˆ์—๋„ ์–ด๋–ค ์Šคํ‚ค๋งˆ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜ ์ฝ”๋“œ๋Š” id="content"์ธ DOM ์š”์†Œ ์•ˆ์˜ ๋‚ด์šฉ์„ ํŒŒ์‹ฑํ•ด์„œ ๊ทธ๊ฑธ ์ดˆ๊ธฐ ๋ฌธ์„œ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์˜ˆ์ œ์ด๋‹ค. ์ด๋•Œ ์‚ฌ์šฉ๋˜๋Š” DOMParser.fromSchema(schema)๋Š” ์Šคํ‚ค๋งˆ์— ์ •์˜๋œ ๊ทœ์น™์„ ๋ฐ”ํƒ•์œผ๋กœ DOM์„ ProseMirror์˜ ๋ฌธ์„œ ๊ตฌ์กฐ๋กœ ๋ณ€ํ™˜ํ•ด์ค€๋‹ค.
import { DOMParser } from "prosemirror-model" import { EditorState } from "prosemirror-state" import { schema } from "prosemirror-schema-basic" let content = document.getElementById("content") let state = EditorState.create({ doc: DOMParser.fromSchema(schema).parse(content) })
JavaScript
๋ณต์‚ฌ
์ฆ‰, HTML ์ฝ˜ํ…์ธ ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ProseMirror ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค๊ณ , ์ด๋ฅผ ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.
์—ฌ๊ธฐ๊นŒ์ง€ ๋ฐฐ์› ๋‹ค๋ฉด ๊ธฐ๋ณธ์ ์ธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ํ•˜์ง€๋งŒ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ์˜ ๊ธฐ๋Šฅ๋“ค์„ ๋” ์ž˜ ํ™œ์šฉํ•˜๋ ค๋ฉด Documents, Document Transformation, Commands ๊ฐœ๋…์— ๋Œ€ํ•ด ๋” ๊นŠ๊ฒŒ ์•Œ์•„์•ผ ํ•œ๋‹ค.

Documents

ProseMirror๋Š” ๋ฌธ์„œ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์ž์ฒด์ ์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•œ๋‹ค. ๋„ํ๋จผํŠธ๋Š” ์—๋””ํ„ฐ์˜ ํ•ต์‹ฌ ์š”์†Œ์ด๋ฉฐ, ์ด ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๋ฉด ProseMirror์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋œ๋‹ค.

Structure

ProseMirror์—์„œ ๋„ํ๋จผํŠธ๋Š” ํ•˜๋‚˜์˜ Node ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋˜๋ฉฐ, ์ด ๋…ธ๋“œ๋Š” 0๊ฐœ ์ด์ƒ์˜ ์ž์‹ ๋…ธ๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” Fragment๋ฅผ ๊ฐ€์ง„๋‹ค. ์ด ๊ตฌ์กฐ๋Š” ์žฌ๊ท€์ ์ด๋ฉฐ ํŠธ๋ฆฌ ํ˜•ํƒœ๋ฅผ ๊ฐ–๋Š”๋‹ค๋Š” ์ ์—์„œ ๋ธŒ๋ผ์šฐ์ €์˜ DOM(Document Object Model)๊ณผ ์œ ์‚ฌํ•˜๋‹ค. ํ•˜์ง€๋งŒ ProseMirror๋Š” ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์—์„œ DOM๊ณผ๋Š” ์ฐจ์ด๋ฅผ ๋ณด์ธ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ HTML ๋ฌธ๋‹จ์ด ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.
<p>This is <strong>strong text with <em>emphasis</em></strong></p>
HTML
๋ณต์‚ฌ
๋ธŒ๋ผ์šฐ์ € DOM์—์„œ๋Š” ์ด๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค‘์ฒฉ๋œ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ํ‘œํ˜„ํ•œ๋‹ค.
์ถœ์ฒ˜: ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๊ฐ€์ด๋“œ ๊ณต์‹๋ฌธ์„œ
ํ•˜์ง€๋งŒ ProseMirror๋Š” ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ ๋ฅผ ๋‚ฉ์ž‘ํ•œ(flat) ์‹œํ€€์Šค ํ˜•ํƒœ๋กœ ๋ชจ๋ธ๋งํ•˜๋ฉฐ, ์Šคํƒ€์ผ(markup)์€ ๊ฐ ํ…์ŠคํŠธ ๋…ธ๋“œ์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(mark)๋กœ ๋ถ™์ธ๋‹ค.
์ถœ์ฒ˜: ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๊ฐ€์ด๋“œ ๊ณต์‹๋ฌธ์„œ
์ด๋Ÿฌํ•œ ๋ชจ๋ธ์€ ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ํ…์ŠคํŠธ๋ฅผ ์ƒ๊ฐํ•˜๊ณ  ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์— ๋” ๊ฐ€๊นŒ์šด ๊ตฌ์กฐ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด
โ€ข
ํŠน์ • ๋ฌธ๋‹จ ๋‚ด์—์„œ ์ปค์„œ ์œ„์น˜๋ฅผ ํŒŒ์•…ํ•  ๋•Œ ๋ฌธ์ž ๋‹จ์œ„ ์˜คํ”„์…‹(offset)์œผ๋กœ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
โ€ข
์Šคํƒ€์ผ ๋ณ€๊ฒฝ์ด๋‚˜ ํ…์ŠคํŠธ ๋ถ„ํ• ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ํ•  ๋•Œ ๋ณต์žกํ•œ ํŠธ๋ฆฌ ์กฐ์ž‘ ์—†์ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•˜๋‹ค.
์ด ๊ตฌ์กฐ ๋•๋ถ„์— ๋ฌธ์„œ๋Š” ํ•ญ์ƒ ํ•˜๋‚˜์˜ ์œ ํšจํ•œ ํ˜•ํƒœ๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
โ€ข
๋™์ผํ•œ mark๋ฅผ ๊ฐ€์ง„ ์ธ์ ‘ํ•œ ํ…์ŠคํŠธ ๋…ธ๋“œ๋Š” ์ž๋™์œผ๋กœ ๋ณ‘ํ•ฉ๋œ๋‹ค.
โ€ข
๋นˆ ํ…์ŠคํŠธ ๋…ธ๋“œ๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.
โ€ข
mark์˜ ์ ์šฉ ์ˆœ์„œ๋Š” schema์—์„œ ์ •์˜๋œ ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.
๊ฒฐ๊ณผ์ ์œผ๋กœ ProseMirror ๋ฌธ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๊ฐ–๋Š”๋‹ค.
โ€ข
ํŠธ๋ฆฌ ํ˜•ํƒœ์˜ ๋ธ”๋ก ๋…ธ๋“œ(block nodes) ๊ตฌ์กฐ
โ€ข
๋Œ€๋ถ€๋ถ„์˜ ๋ง๋‹จ ๋…ธ๋“œ๋Š” textblock(ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ธ”๋ก ๋…ธ๋“œ)
โ€ข
๋ง๋‹จ์ด์ง€๋งŒ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๋ธ”๋ก ๋…ธ๋“œ๋„ ์กด์žฌํ•จ โ†’ ์˜ˆ: hr, video
๊ฐ ๋…ธ๋“œ ๊ฐ์ฒด(Node object)๋Š” ์ž์‹ ์˜ ์—ญํ• ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์—ฌ๋Ÿฌ ์†์„ฑ์„ ๊ฐ€์ง„๋‹ค.
โ€ข
isBlock: ๋ธ”๋ก ๋…ธ๋“œ์ธ์ง€ ์—ฌ๋ถ€
โ€ข
isInline: ์ธ๋ผ์ธ ๋…ธ๋“œ์ธ์ง€ ์—ฌ๋ถ€
โ€ข
inlineContent: ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ ๋ฅผ ์ž์‹์œผ๋กœ ํ—ˆ์šฉํ•˜๋Š”์ง€ ์—ฌ๋ถ€
โ€ข
isTextblock: ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ธ”๋ก ๋…ธ๋“œ์ธ์ง€ ์—ฌ๋ถ€
โ€ข
isLeaf: ์ž์‹ ๋…ธ๋“œ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์—†๋Š” ๋ง๋‹จ ๋…ธ๋“œ์ธ์ง€ ์—ฌ๋ถ€
์˜ˆ์‹œ๋กœ
โ€ข
"paragraph"๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ isTextblock ์†์„ฑ์„ ๊ฐ–๋Š”๋‹ค.
โ€ข
"blockquote"๋Š” ๋‹ค๋ฅธ ๋ธ”๋ก ๋…ธ๋“œ๋ฅผ ์ž์‹์œผ๋กœ ๊ฐ–๋Š” ๋ธ”๋ก ๋…ธ๋“œ์ด๋‹ค.
โ€ข
text, hard_break, inline_image๋Š” ์ธ๋ผ์ธ ๋ง๋‹จ ๋…ธ๋“œ
โ€ข
horizontal_rule๋Š” ๋ธ”๋ก ๋ง๋‹จ ๋…ธ๋“œ
๋งˆ์ง€๋ง‰์œผ๋กœ, ์Šคํ‚ค๋งˆ๋Š” "์ด ๋…ธ๋“œ๊ฐ€ ์–ด๋–ค ์ž์‹ ๋…ธ๋“œ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š”์ง€"์— ๋Œ€ํ•œ ์„ธ๋ถ€ ๊ทœ์น™์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์–ด๋–ค ๋…ธ๋“œ๊ฐ€ ๋ธ”๋ก ์ฝ˜ํ…์ธ ๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค๊ณ  ํ•ด์„œ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๋ธ”๋ก ๋…ธ๋“œ๋ฅผ ์ž์‹์œผ๋กœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ๊ตฌ์กฐ ๋•๋ถ„์— ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ๋Š” ์„ฑ๋Šฅ, ์ปค์„œ ์œ„์น˜ ์ถ”์ , ๋งˆํฌ ๋ณ‘ํ•ฉ ์ฒ˜๋ฆฌ ๋“ฑ์—์„œ ์ •๊ตํ•˜๋ฉด์„œ๋„ ํšจ์œจ์ ์ธ ์—๋””ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

Identity and persistence

DOM ํŠธ๋ฆฌ์™€ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๋ฌธ์„œ ์‚ฌ์ด์˜ ๋˜ ๋‹ค๋ฅธ ์ค‘์š”ํ•œ ์ฐจ์ด๋Š” ๋…ธ๋“œ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€์— ์žˆ๋‹ค.
๋ธŒ๋ผ์šฐ์ €์˜ DOM์—์„œ๋Š” ๋…ธ๋“œ๊ฐ€ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„(mutable) ๊ฐ์ฒด์ด๋ฉฐ, ๊ณ ์œ ํ•œ ์ •์ฒด์„ฑ(identity)์„ ๊ฐ€์ง„๋‹ค. ์ด ๋ง์€ ์ฆ‰, ์–ด๋–ค DOM ๋…ธ๋“œ๋Š” ์˜ค์ง ํ•˜๋‚˜์˜ ๋ถ€๋ชจ ๋…ธ๋“œ์—๋งŒ ์†ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์—…๋ฐ์ดํŠธ๊ฐ€ ์ผ์–ด๋‚  ๋•Œ ๊ธฐ์กด ๋…ธ๋“œ ๊ฐ์ฒด๊ฐ€ ๋ณ€๊ฒฝ(mutate)๋œ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.
ํ•˜์ง€๋งŒ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ์—์„œ๋Š” ๋‹ค๋ฅด๋‹ค. ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ์˜ ๋…ธ๋“œ๋Š” ๋‹จ์ง€ ๊ฐ’(value)์ผ ๋ฟ์ด๋‹ค. ์ด๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ˆซ์ž 3์„ ๋‹ค๋ฃฐ ๋•Œ์™€ ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค. ์ˆซ์ž 3์€ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ ๋™์‹œ์— ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๊ณ , ์–ด๋–ค ํŠน์ • ๊ตฌ์กฐ์— ํฌํ•จ๋˜์—ˆ๋‹ค๋Š” ๋งํฌ๋‚˜ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š์œผ๋ฉฐ, ๊ฑฐ๊ธฐ์— 1์„ ๋”ํ•˜๋ฉด ์›๋ž˜ 3์„ ๋ฐ”๊พธ์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ๊ฐ’์ธ 4๊ฐ€ ์ƒ๊ธด๋‹ค.
ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๋„ํ๋จผํŠธ์˜ ์กฐ๊ฐ(pieces)๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค. ๋„ํ๋จผํŠธ ์กฐ๊ฐ์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ ์กฐ๊ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๊ธฐ์ค€์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค. ๊ทธ๋“ค์€ ์ž์‹ ์ด ์–ด๋–ค ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์— ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ์•Œ์ง€ ๋ชปํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ ๊ตฌ์กฐ์— ๋™์‹œ์— ํฌํ•จ๋˜๊ฑฐ๋‚˜, ํ•˜๋‚˜์˜ ๊ตฌ์กฐ ๋‚ด์— ์—ฌ๋Ÿฌ ๋ฒˆ ๋“ฑ์žฅํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ฆ‰, ์ƒํƒœ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ, ๊ฐ’์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.
์ด๋Ÿฌํ•œ ํŠน์ง• ๋•Œ๋ฌธ์—, ๋„ํ๋จผํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋งˆ๋‹ค ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐ”๋€Œ์ง€ ์•Š์€ ํ•˜์œ„ ๋…ธ๋“œ๋“ค์€ ์ด์ „ ๋„ํ๋จผํŠธ์™€ ๊ณต์œ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ์ž‘์—…์€ ๋น„๊ต์  ๋น„์šฉ์ด ์ €๋ ดํ•˜๋‹ค (๋ฉ”๋ชจ๋ฆฌ/์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ).
์˜ˆ์‹œ
์ด๋Ÿฐ ๋„ํ๋จผํŠธ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ, โ€˜Helloโ€™๋ฅผ โ€˜Hiโ€™๋กœ ๋ฐ”๊พธ๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.
const doc1 = { type: "doc", content: [ { type: "paragraph", content: [{ type: "text", text: "Hello" }] }, { type: "paragraph", content: [{ type: "text", text: "World" }] }, ] }
JavaScript
๋ณต์‚ฌ
// ์ƒˆ๋กœ ๋งŒ๋“  ๋…ธ๋“œ const newParagraph = { type: "paragraph", content: [{ type: "text", text: "Hi" }] } // ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ: ๋‚˜๋จธ์ง€๋Š” doc1์—์„œ ์žฌ์‚ฌ์šฉ const doc2 = { type: "doc", content: [ newParagraph, // ์ƒˆ๋กœ ๋งŒ๋“  ๋…ธ๋“œ doc1.content[1] // ๊ธฐ์กด ๋…ธ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฐธ์กฐ ] }
JavaScript
๋ณต์‚ฌ
์ฆ‰, doc2๋Š” ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ์ „์ฒด ๋ณต์‚ฌํ•œ ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋ฐ”๋€ ๋ถ€๋ถ„๋งŒ ์ƒˆ๋กœ ๋งŒ๋“ค๊ณ  ๋‚˜๋จธ์ง€๋Š” ๊ธฐ์กด ๋ฌธ์„œ์˜ ๋…ธ๋“œ ์ฐธ์กฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•œ ๊ฒƒ์ด๋‹ค.
์ด๋Ÿฐ ์„ค๊ณ„๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์žฅ์ ์„ ๊ฐ€์ง„๋‹ค.
โ€ข
์—…๋ฐ์ดํŠธ ๋„์ค‘ ์—๋””ํ„ฐ๊ฐ€ ์ž˜๋ชป๋œ ์ค‘๊ฐ„ ์ƒํƒœ์— ๋น ์ง€๋Š” ์ผ์ด ์—†์–ด์ง„๋‹ค.
์ƒˆ๋กœ์šด ์ƒํƒœ๋Š” ์™„์ „ํžˆ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง€๋ฉฐ, ๊ธฐ์กด ์ƒํƒœ๋ฅผ ๋ฐ”๋กœ ๊ต์ฒดํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
โ€ข
๋ฌธ์„œ๋ฅผ ์ˆ˜ํ•™์ ์œผ๋กœ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ์‚ฌ๊ณ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
(๊ฐ’์ด ๋‚ด๋ถ€์—์„œ ์ž๊พธ ๋ณ€ํ•˜๋ฉด reasoning์ด ์–ด๋ ต์ง€๋งŒ, ProseMirror์—์„œ๋Š” ๊ทธ๋ ‡์ง€ ์•Š๋‹ค.)
โ€ข
์ด๋Š” ํ˜‘์—… ํŽธ์ง‘ ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ณ ,
ProseMirror๊ฐ€ ๋งค์šฐ ํšจ์œจ์ ์ธ DOM ์—…๋ฐ์ดํŠธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
(ํ™”๋ฉด์— ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ Œ๋”๋งํ•œ ๋ฌธ์„œ์™€ ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ๋น„๊ตํ•˜๋Š” ๋ฐฉ์‹)
๋‹จ, ์ด๋Ÿฌํ•œ ๋…ธ๋“œ๋“ค์€ ์ผ๋ฐ˜์ ์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋˜๊ธฐ ๋•Œ๋ฌธ์—, Object.freeze ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ•์ œ๋กœ ๋ถˆ๋ณ€์„ฑ์„ ๋ถ€์—ฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ์—์„œ Object.freeze๋กœ ๋…ธ๋“œ๋ฅผ ๋™๊ฒฐํ•˜๋ฉด ์™œ ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๊ฒŒ ๋ ๊นŒ?
๊ทธ๋ž˜์„œ ์‹ค์ œ๋กœ๋Š” ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด โ€˜๊ฐ€๋Šฅโ€™ํ•˜๊ธด ํ•˜์ง€๋งŒ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. ๋…ธ๋“œ๋“ค์€ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ ๊ณต์œ ๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ•˜๋‚˜๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ตฐ๋ฐ๊ฐ€ ๋™์‹œ์— ๋ง๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ๋…ธ๋“œ ๊ฐ์ฒด ๋‚ด๋ถ€์— ๋“ค์–ด์žˆ๋Š” ์†์„ฑ ๊ฐ์ฒด(attrs)๋‚˜ ์ž์‹ ๋…ธ๋“œ ๋ฐฐ์—ด(fragments) ๊ฐ™์€ ๋ถ€๋ถ„๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค. ์ด๋“ค๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.
ProseMirror ๋ฌธ์„œ์˜ ๋…ธ๋“œ๋Š” ์ˆซ์ž์™€ ๊ฐ™์ด ๋‹ค๋ฃจ์–ด์•ผ ํ•˜๋Š” ๋ถˆ๋ณ€ ๊ฐ’์ด๋‹ค.
์ง์ ‘ ๋ณ€๊ฒฝํ•˜์ง€ ๋ง๊ณ , ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด ๊ต์ฒดํ•ด์•ผ ํ•œ๋‹ค.

Data structures

์ด์ œ ProseMirror์˜ ๋„ํ๋จผํŠธ ๊ตฌ์กฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ์•Œ์•„๋ณด์ž.
ProseMirror์—์„œ ๋„ํ๋จผํŠธ๋Š” ๊ฒฐ๊ตญ Node ๊ฐ์ฒด ํ•˜๋‚˜๋‹ค. ์ด ๊ฐ์ฒด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค.
์ถœ์ฒ˜: ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๊ฐ€์ด๋“œ ๊ณต์‹๋ฌธ์„œ
1.
Node ์ธ์Šคํ„ด์Šค: ๊ฐ ๋…ธ๋“œ๋Š” Node ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค. ์ด ๋…ธ๋“œ๋Š” type์ด๋ผ๋Š” ์ •๋ณด๋ฅผ ๊ฐ–๊ณ  ์žˆ๊ณ , ์ด๊ฑด ํ•ด๋‹น ๋…ธ๋“œ๊ฐ€ ์–ด๋–ค ๋…ธ๋“œ์ธ์ง€ ์•Œ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. (์˜ˆ: paragraph, heading ๋“ฑ).
2.
type (NodeType): ๋…ธ๋“œ์˜ ์ด๋ฆ„, ๊ฐ€๋Šฅํ•œ ์†์„ฑ(attribute), ์–ด๋–ค ๋‚ด์šฉ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค. ๊ฐ™์€ schema์— ์†ํ•˜๋Š” ์—ฌ๋Ÿฌ ๋…ธ๋“œ๋“ค์ด ํƒ€์ž…๋ณ„๋กœ ์ •์˜๋ผ ์žˆ๋‹ค.
3.
content (Fragment): ๋…ธ๋“œ ์•ˆ์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์ž์‹ ๋…ธ๋“œ๋“ค์„ Fragment๋ผ๋Š” ๊ฐ์ฒด๋กœ ๋ฌถ์–ด์„œ ๋ณด๊ด€ํ•œ๋‹ค. ๋น„์–ด ์žˆ๋Š” ๋…ธ๋“œ(์˜ˆ: ์ˆ˜ํ‰์„ )๋„ ์‹ค์ œ๋กœ๋Š” empty fragment๋ผ๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ€์ง„๋‹ค.
4.
attrs (Attributes): ๋…ธ๋“œ์˜ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋“ค. ์˜ˆ: ์ด๋ฏธ์ง€ ๋…ธ๋“œ์˜ alt, src ์†์„ฑ ๋“ฑ.
5.
marks: ์ธ๋ผ์ธ ๋…ธ๋“œ์—๋งŒ ์กด์žฌํ•œ๋‹ค. ์˜ˆ: โ€œstrongโ€(๊ตต๊ฒŒ), โ€œemโ€(๊ธฐ์šธ์ž„), โ€œlinkโ€ ๊ฐ™์€ ์Šคํƒ€์ผ ์ •๋ณด๋“ค์ด ๋ฐฐ์—ด๋กœ ์ €์žฅ๋จ.
๊ฒฐ๊ตญ ์ „์ฒด ๋„ํ๋จผํŠธ๋„ ํ•˜๋‚˜์˜ Node(โ€œdocโ€ ํƒ€์ž…์˜ ๋ฃจํŠธ ๋…ธ๋“œ)์ผ ๋ฟ์ด๋‹ค. ์ด ๋ฃจํŠธ ๋…ธ๋“œ์˜ ์ž์‹์œผ๋กœ ์—ฌ๋Ÿฌ ๋ธ”๋ก ๋…ธ๋“œ๋“ค์ด ๋“ค์–ด๊ฐ€๊ณ , ๋ธ”๋ก ๋…ธ๋“œ ์•ˆ์—๋Š” ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ ๊ฐ€ ๋“ค์–ด๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค. ์‹ฌ์ง€์–ด ๋ฌธ์„œ ์ „์ฒด๊ฐ€ ์ธ๋ผ์ธ ์ฝ˜ํ…์ธ  ํ•˜๋‚˜๋งŒ ์žˆ์–ด๋„ ๋œ๋‹ค. (๋ฃจํŠธ๊ฐ€ textblock์ด ๋  ์ˆ˜๋„ ์žˆ๋‹ค.)
์•„๋ž˜ ์ฝ”๋“œ๋Š” ProseMirror ๊ธฐ๋ณธ schema๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋„ํ๋จผํŠธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค. One.+ ์ˆ˜ํ‰์„  + Two!์ด 3๊ฐœ์˜ ๋…ธ๋“œ๊ฐ€ ํ•œ ๋„ํ๋จผํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ๋‹ค.
import {schema} from "prosemirror-schema-basic" // (The null arguments are where you can specify attributes, if necessary.) let doc = schema.node("doc", null, [ schema.node("paragraph", null, [schema.text("One.")]), schema.node("horizontal_rule"), schema.node("paragraph", null, [schema.text("Two!")]) ])
JavaScript
๋ณต์‚ฌ

Indexing

ProseMirror์˜ ๋…ธ๋“œ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ์ธ๋ฑ์‹ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” DOM์ฒ˜๋Ÿผ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์ด๊ณ , ๋‘ ๋ฒˆ์งธ๋Š” ํ‰๋ฉด์ ์ธ(flatํ•œ) ํ† ํฐ ์‹œํ€€์Šค๋กœ ๋„ํ๋จผํŠธ๋ฅผ ์ทจ๊ธ‰ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

1. ํŠธ๋ฆฌ ๊ธฐ๋ฐ˜ ์ธ๋ฑ์‹ฑ

์ด ๋ฐฉ์‹์€ DOM์—์„œ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๊ฐœ๋ณ„ ๋…ธ๋“œ์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜, ์ž์‹ ๋…ธ๋“œ๋ฅผ ๋ฐ”๋กœ ์กฐํšŒํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์— ์œ ์šฉํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด child ๋ฉ”์„œ๋“œ๋‚˜ childCount๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๋…ธ๋“œ์˜ ์ž์‹์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ „์ฒด ๋ฌธ์„œ๋ฅผ ์ˆœํšŒํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” descendants๋‚˜ nodesBetween ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์žฌ๊ท€ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋ฌธ์„œ๋ฅผ ํŠธ๋ฆฌ์ฒ˜๋Ÿผ ํƒ์ƒ‰ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด์„œ descendants ๋ฉ”์„œ๋“œ๋กœ ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ highlight๋ผ๋Š” ํƒ€์ž… ์ด๋ฆ„์œผ๋กœ ํ•˜์ด๋ผ์ดํŠธ ๋…ธ๋“œ๋ฅผ ์ฐพ์•„์„œ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์„ ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
editor.state.doc.descendants((node) => { if (node.type.name === 'highlight') { // hightlight ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…... } });
TypeScript
๋ณต์‚ฌ

2. ํ† ํฐ ์‹œํ€€์Šค ๊ธฐ๋ฐ˜ ์ธ๋ฑ์‹ฑ

์ด ๋ฐฉ์‹์€ ๋„ํ๋จผํŠธ์˜ ํŠน์ • ์œ„์น˜๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ํ›จ์”ฌ ์œ ์šฉํ•˜๋‹ค. ๋„ํ๋จผํŠธ์˜ ๋ชจ๋“  ์œ„์น˜๋ฅผ ํ•˜๋‚˜์˜ ์ •์ˆ˜๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š”๋ฐ, ์ด ์ •์ˆ˜๋Š” ์‹ค์ œ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ํ† ํฐ ์ˆœ์„œ์— ๋”ฐ๋ฅธ ์ธ๋ฑ์Šค์ผ ๋ฟ์ด๋‹ค. ์‹ค์ œ๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ํ† ํฐ ๊ฐ์ฒด๊ฐ€ ์žˆ๋Š” ๊ฑด ์•„๋‹ˆ๊ณ , ๋„ํ๋จผํŠธ์˜ ํŠธ๋ฆฌ ๊ตฌ์กฐ์™€ ๊ฐ ๋…ธ๋“œ๊ฐ€ ์ž๊ธฐ ํฌ๊ธฐ(nodeSize)๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ํ™œ์šฉํ•ด ์ •์ˆ˜ ๊ธฐ๋ฐ˜ ์œ„์น˜ ์ ‘๊ทผ์„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์œ„์น˜ ๊ทœ์น™

โ€ข
๋ฌธ์„œ์˜ ์‹œ์ž‘(์ฒซ ์ฝ˜ํ…์ธ  ๋ฐ”๋กœ ์•ž)์€ position 0์ด๋‹ค.
โ€ข
์ฝ˜ํ…์ธ ๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ๋…ธ๋“œ์— ๋“ค์–ด๊ฐ€๊ฑฐ๋‚˜ ๋‚˜์˜ค๋Š” ์ง€์ ์€ ํ† ํฐ 1๊ฐœ๋กœ ์„ผ๋‹ค.
โ—ฆ
์˜ˆ: ๋ฌธ์„œ๊ฐ€ <p>๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด, ๊ทธ ๋ฌธ๋‹จ์˜ ์‹œ์ž‘์€ position 1์ด๋‹ค.
โ€ข
ํ…์ŠคํŠธ ๋…ธ๋“œ์˜ ๋ฌธ์ž ํ•˜๋‚˜๋งˆ๋‹ค ํ† ํฐ 1๊ฐœ๋กœ ์„ผ๋‹ค.
โ—ฆ
์˜ˆ: โ€œhiโ€๋ผ๋ฉด, โ€œhโ€ ๋‹ค์Œ์ด position 2, โ€œiโ€ ๋‹ค์Œ์ด position 3์ด๋‹ค.
โ€ข
์ฝ˜ํ…์ธ ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฆฌํ”„ ๋…ธ๋“œ(์˜ˆ: ์ด๋ฏธ์ง€)๋Š” ํ† ํฐ 1๊ฐœ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.
์˜ˆ์‹œ
HTML๋กœ ํ‘œํ˜„๋œ ๋ฌธ์„œ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค๊ณ  ํ•˜์ž.
<p>One</p> <blockquote><p>Two<img src="..."></p></blockquote>
HTML
๋ณต์‚ฌ
์ด ๋ฌธ์„œ๋ฅผ ํ† ํฐ ์‹œํ€€์Šค๋กœ ํ‘œํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. ์ฆ‰, ๊ฐ ๋…ธ๋“œ๋Š” ์‹œ์ž‘ ํƒœ๊ทธ, ๋ฌธ์ž, ๋‹ซ๋Š” ํƒœ๊ทธ ๋“ฑ์œผ๋กœ ํ•˜๋‚˜์˜ ์‹œํ€€์Šค์ฒ˜๋Ÿผ ๊ณ„์‚ฐ๋œ๋‹ค.
0 1 2 3 4 5 ย <p> O n e </p> 5 6 7 8 9 10 11 12 13 <blockquote> <p> T w o <img> </p> </blockquote>
Plain Text
๋ณต์‚ฌ
๋…ธ๋“œ ํฌ๊ธฐ ์ •๋ณด
โ€ข
๊ฐ ๋…ธ๋“œ๋Š” .nodeSize ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ž์‹  ์ „์ฒด์˜ ํฌ๊ธฐ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
โ€ข
.content.size๋Š” ๋…ธ๋“œ์˜ ์ž์‹ ์ฝ˜ํ…์ธ ์˜ ํฌ๊ธฐ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
โ€ข
๋ฌธ์„œ ๋ฃจํŠธ ๋…ธ๋“œ์˜ ๊ฒฝ์šฐ์—๋Š” <doc> ์ž์ฒด์˜ ์‹œ์ž‘/์ข…๋ฃŒ ํ† ํฐ์€ ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ doc.nodeSize๊ฐ€ ์•„๋‹ˆ๋ผ doc.content.size๊ฐ€ ์‹ค์ œ ๋ฌธ์„œ์˜ ํฌ๊ธฐ๋‹ค. (๋ฌธ์„œ ๋ฐ”๊นฅ์—๋Š” ์ปค์„œ๋ฅผ ๋‘˜ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ)

์œ„์น˜ ํ•ด์„์„ ์‰ฝ๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•

๋ฌธ์„œ ๋‚ด ํŠน์ • ์œ„์น˜๋ฅผ ์‚ฌ๋žŒ์ด ์ง์ ‘ ๊ณ„์‚ฐํ•˜๋ ค๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๋ณต์žกํ•˜๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” Node.resolve(position)์„ ํ˜ธ์ถœํ•˜๋ฉด ResolvedPos๋ผ๋Š” ๊ตฌ์กฐ์ฒด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ตฌ์กฐ์ฒด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
โ€ข
ํ˜„์žฌ ์œ„์น˜์˜ ๋ถ€๋ชจ ๋…ธ๋“œ
โ€ข
๊ทธ ๋ถ€๋ชจ ๋‚ด์—์„œ์˜ offset
โ€ข
๋ถ€๋ชจ์˜ ์กฐ์ƒ ๋…ธ๋“œ๋“ค
โ€ข
๊ทธ ์™ธ ๋ช‡ ๊ฐ€์ง€ ์ •๋ณด
๋˜๋Š” Prosemirror Debugger Tool์ด๋ผ๋Š” ํฌ๋กฌ ์ต์Šคํ…์…˜์„ ํ™œ์šฉํ•˜๋ฉด ๋””๋ฒ„๊น…ํ•˜๊ธฐ ํŽธํ•˜๋‹ค.
์ฃผ์˜์ 
โ€ข
childCount๋กœ ์ ‘๊ทผํ•˜๋Š” ์ž์‹ ์ธ๋ฑ์Šค
โ€ข
๋ฌธ์„œ ์ „์ฒด์—์„œ์˜ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” position
โ€ข
ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘์ธ ๋…ธ๋“œ ๋‚ด๋ถ€์—์„œ์˜ ์œ„์น˜์ธ node-local offset
์ด ์„ธ ๊ฐ€์ง€๋Š” ๋ชจ๋‘ ๋‹ค๋ฅด๋ฏ€๋กœ ์ฃผ์˜ํ•ด์„œ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•œ๋‹ค.
์š”์•ฝํ•˜์ž๋ฉด, ProseMirror๋Š” ํŠธ๋ฆฌ ๊ธฐ๋ฐ˜ ํƒ์ƒ‰๊ณผ ํ† ํฐ ์‹œํ€€์Šค ๊ธฐ๋ฐ˜ ํƒ์ƒ‰ ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉฐ, ์ด ๋•๋ถ„์— ์œ ์—ฐํ•œ ์œ„์น˜ ์ œ์–ด์™€ ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

Slices

๋ณต๋ถ™์ด๋‚˜ ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด, ๋„ํ๋จผํŠธ์—์„œ ๋‘ ์œ„์น˜ ์‚ฌ์ด์˜ ์ฝ˜ํ…์ธ , ์ฆ‰ ๋„ํ๋จผํŠธ์˜ ์ผ๋ถ€๋ถ„(slice) ์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์Šฌ๋ผ์ด์Šค๋Š” ์ „์ฒด ๋…ธ๋“œ(node)๋‚˜ ํ”„๋ž˜๊ทธ๋จผํŠธ(fragment)์™€๋Š” ๋‹ค๋ฅด๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์Šฌ๋ผ์ด์Šค์˜ ์‹œ์ž‘ ๋˜๋Š” ๋์— ์žˆ๋Š” ๋…ธ๋“œ๋“ค์ด โ€˜์—ด๋ ค ์žˆ์„ ์ˆ˜(open)โ€™ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ ๋ฌธ๋‹จ์˜ ์ค‘๊ฐ„๋ถ€ํ„ฐ ๋‹ค์Œ ๋ฌธ๋‹จ์˜ ์ค‘๊ฐ„๊นŒ์ง€ ํ…์ŠคํŠธ๋ฅผ ์„ ํƒํ•˜๋ฉด, ์Šฌ๋ผ์ด์Šค์—๋Š” ๋‘ ๊ฐœ์˜ ๋ฌธ๋‹จ์ด ํฌํ•จ๋˜์ง€๋งŒ, ์ฒซ ๋ฒˆ์งธ ๋ฌธ๋‹จ์€ ์‹œ์ž‘์ด ์—ด๋ฆฐ(open at the start) ์ƒํƒœ์ด๊ณ , ๋‘ ๋ฒˆ์งธ ๋ฌธ๋‹จ์€ ๋์ด ์—ด๋ฆฐ(open at the end) ์ƒํƒœ๋‹ค. ๋ฐ˜๋ฉด, ์ „์ฒด ๋ฌธ๋‹จ์„ ์„ ํƒํ•œ ๊ฒฝ์šฐ๋Š” ๋‹ซํžŒ(closed) ๋…ธ๋“œ๋ฅผ ์„ ํƒํ•œ ๊ฒƒ์ด๋‹ค.
์—ด๋ ค ์žˆ๋Š” ๋…ธ๋“œ๋Š” ์Šฌ๋ผ์ด์Šค๋กœ ๋‹ค๋ฃฐ ๋•Œ ์Šคํ‚ค๋งˆ ์ œ์•ฝ ์กฐ๊ฑด์„ ์œ„๋ฐ˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋ฌธ๋‹จ ์ „์ฒด๊ฐ€ ์•„๋‹Œ ์ผ๋ถ€๋งŒ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด์„œ, ์›๋ž˜ ์Šคํ‚ค๋งˆ์—์„œ ์š”๊ตฌํ•˜๋Š” ํ•„์ˆ˜ ๋…ธ๋“œ๋“ค์ด ์Šฌ๋ผ์ด์Šค ๋ฐ–์— ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์ด๋Ÿฐ ๋ถˆ์™„์ „ํ•œ ๋ฌธ์„œ ์กฐ๊ฐ(slices)์„ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ProseMirror์—์„œ๋Š” Slice๋ผ๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
Slice๋Š” ๋‹ค์Œ์„ ํฌํ•จํ•œ๋‹ค.
โ€ข
ํ•˜๋‚˜์˜ Fragment (์ž์‹ ๋…ธ๋“œ์˜ ์ง‘ํ•ฉ)
โ€ข
์–‘์ชฝ ๋์— ๋Œ€ํ•œ open depth ์ •๋ณด (openStart, openEnd)
โ—ฆ
์ด ๊ฐ’์€ ํ•ด๋‹น ์Šฌ๋ผ์ด์Šค๊ฐ€ ์–ด๋А ๊นŠ์ด๊นŒ์ง€ ์—ด๋ ค ์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค
์Šฌ๋ผ์ด์Šค๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Node.slice ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
์˜ˆ์‹œ
// doc holds two paragraphs, containing text "a" and "b" let slice1 = doc.slice(0, 3) // The first paragraph console.log(slice1.openStart, slice1.openEnd) // โ†’ 0 0
JavaScript
๋ณต์‚ฌ
openStart์™€ openEnd๊ฐ€ 0์ด๋ฏ€๋กœ, ์ด ์Šฌ๋ผ์ด์Šค๋Š” ์™„์ „ํžˆ ๋‹ซํžŒ(closed) ์ƒํƒœ์˜ ๋…ธ๋“œ์ด๋‹ค.
let slice2 = doc.slice(1, 5) // From start of first paragraph // to end of second console.log(slice2.openStart, slice2.openEnd) // โ†’ 1 1
JavaScript
๋ณต์‚ฌ
openStart์™€ openEnd๊ฐ€ 1์ด๋ฏ€๋กœ, ์–‘์ชฝ ๋ชจ๋‘ ํ•˜์œ„ ๋…ธ๋“œ๊ฐ€ ์—ด๋ ค ์žˆ๋Š”(open) ์ƒํƒœ์ด๋‹ค.
์ฆ‰, ์ด ์Šฌ๋ผ์ด์Šค๋Š” ๋‘ ๋ฌธ๋‹จ์˜ ์ค‘๊ฐ„๋ถ€ํ„ฐ ์ค‘๊ฐ„๊นŒ์ง€ ์ž˜๋ผ๋‚ธ ๋ถ€๋ถ„์ด๋‹ค.
ProseMirror์—์„œ ์Šฌ๋ผ์ด์Šค๋Š” ์™„์ „ํ•œ ๋…ธ๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์—ด๋ ค ์žˆ์„ ์ˆ˜๋„ ์žˆ๋Š” ๋ถ€๋ถ„์ ์ธ ๋ฌธ์„œ ์กฐ๊ฐ์„ ํ‘œํ˜„ํ•˜๋ฉฐ, ๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

Changing

ProseMirror์˜ Node์™€ Fragment๋Š” ์˜์†์ (persistent) ์ž๋ฃŒ๊ตฌ์กฐ์ด๋‹ค. ์ฆ‰, ์ ˆ๋Œ€๋กœ ์ง์ ‘ ์ˆ˜์ •(mutate)ํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค. ํ•œ ๋ฒˆ ์–ป์€ ๋ฌธ์„œ, ๋…ธ๋“œ, ํ”„๋ž˜๊ทธ๋จผํŠธ ๊ฐ์ฒด๋Š” ํ•ญ์ƒ ๋™์ผํ•œ ์ƒํƒœ๋กœ ์œ ์ง€๋˜๋ฉฐ, ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค.
๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ๋„ํ๋จผํŠธ๋ฅผ ๊ฐฑ์‹ ํ•  ๋•Œ๋Š” ์ง์ ‘ ๋…ธ๋“œ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ ,
transform ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด ๋ณ€ํ™˜(transformation) ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋„ํ๋จผํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์„ ๋ฟ ์•„๋‹ˆ๋ผ, ๋ฌด์—‡์ด ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ๊ธฐ๋ก๋„ ๋‚จ๊ธฐ๊ฒŒ ๋˜์–ด, ํ•ด๋‹น ๋„ํ๋จผํŠธ๊ฐ€ editor state์˜ ์ผ๋ถ€์ผ ๋•Œ ํ•„์ˆ˜์ ์ด๋‹ค.
ํ•˜์ง€๋งŒ transform์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ ๋ฒ„์ „์„ ์ˆ˜๋™์œผ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ๋„ ์žˆ๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ, Node์™€ Fragment์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ—ฌํผ ๋ฉ”์„œ๋“œ๋“ค์ด ์ค€๋น„๋˜์–ด ์žˆ๋‹ค.
1.
๋ฌธ์„œ ์ „์ฒด๋ฅผ ๊ฐฑ์‹ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข
Node.replace: ๋ฌธ์„œ์˜ ํŠน์ • ๋ฒ”์œ„๋ฅผ ์ƒˆ๋กœ์šด Slice๋กœ ๊ต์ฒดํ•œ๋‹ค. ์ „์ฒด ๋ฌธ์„œ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
2.
ํŠน์ • ๋…ธ๋“œ๋งŒ ์–•๊ฒŒ(shallow) ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข
Node.copy: ๊ธฐ์กด ๋…ธ๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, ์ƒˆ๋กœ์šด ์ฝ˜ํ…์ธ ๋งŒ ๋„ฃ์–ด์„œ ์œ ์‚ฌํ•œ ๋…ธ๋“œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
3.
ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ
โ€ข
Fragment.replaceChild: ์ž์‹ ๋…ธ๋“œ๋ฅผ ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ ๊ต์ฒด
โ€ข
Fragment.append: ์ƒˆ๋กœ์šด ๋…ธ๋“œ๋ฅผ ๋’ค์— ์ถ”๊ฐ€
ProseMirror์—์„œ๋Š” ๋ฌธ์„œ ๊ตฌ์กฐ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ , ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋Œ€์ฒดํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ถˆ๋ณ€์„ฑ(immutability)์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„, ๋ฌธ์„œ ์ƒํƒœ์˜ ์ถ”์ ๊ณผ ํ˜‘์—…(์ฝœ๋ผ๋ณด ํŽธ์ง‘ ๊ธฐ๋Šฅ)์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

Document transformations

Documents์˜ changing์—์„œ ๋งํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ transform์„ ์‚ฌ์šฉํ•˜๋ฉด ๋„ํ๋จผํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋ณ€๊ฒฝ ๊ธฐ๋ก๋„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ๋•Œ๋ฌธ์— ๋ชจ๋“  transaction์˜ ๊ธฐ๋ฐ˜์ด ๋˜๋ฉฐ, ๋˜๋Œ๋ฆฌ๊ธฐ(undo) ๊ธฐ๋Šฅ์ด๋‚˜ ํ˜‘์—… ํŽธ์ง‘(collaborative editing) ๊ธฐ๋Šฅ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

์™œ transform์ด ํ•„์š”ํ• ๊นŒ?

โ€œ๊ทธ๋ƒฅ ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ๋ฐ”๊พธ๋ฉด ์•ˆ ๋˜๋Š” ๊ฑธ๊นŒ?โ€, โ€œ์•„๋‹ˆ๋ฉด ์ƒˆ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์—๋””ํ„ฐ์— ๋„ฃ๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋ƒ?โ€ ๋ผ๋Š” ์งˆ๋ฌธ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š๋Š” ๋ฐ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ด์œ ๊ฐ€ ์žˆ๋‹ค.
โ€ข
์ฝ”๋“œ ๋ช…ํ™•์„ฑ(Code Clarity): ๋ถˆ๋ณ€(immutable) ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋Š” ์ฝ”๋“œ์˜ ํ๋ฆ„์„ ๋” ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ , ๋ฒ„๊ทธ๋ฅผ ์ค„์ธ๋‹ค.
โ€ข
์—…๋ฐ์ดํŠธ ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ก: Transform ์‹œ์Šคํ…œ์€ ๋‹จ์ˆœํžˆ ๋ฌธ์„œ๋ฅผ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ด์ „ ๋ฌธ์„œ์—์„œ ์ƒˆ ๋ฌธ์„œ๋กœ ๋ฐ”๋€Œ๊ธฐ๊นŒ์ง€์˜ ๋ณ€ํ™” ์ด๋ ฅ(steps) ์„ ์ €์žฅํ•œ๋‹ค. ์–ด๋–ค ํŽธ์ง‘์ด ์ผ์–ด๋‚ฌ๋Š”์ง€๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
์ด๋Ÿฌํ•œ ๋ณ€ํ™”๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์— ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.
โ€ข
๊ฐ step์˜ ์—ญ์—ฐ์‚ฐ(inverse) ์„ ์ ์šฉํ•˜๋ฉด ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค. ProseMirror๋Š” ๋‹จ์ˆœํžˆ ์ด์ „ ์ƒํƒœ๋กœ ๋กค๋ฐฑํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋ณต์žกํ•œ ์„ ํƒ์  ๋˜๋Œ๋ฆฌ๊ธฐ(selective undo) ๋ฅผ ์ง€์›ํ•œ๋‹ค.
โ€ข
๊ฐ ์‚ฌ์šฉ์ž์˜ step์„ ๋‹ค๋ฅธ ์—๋””ํ„ฐ์—๊ฒŒ ์ „๋‹ฌํ•˜๊ณ , ํ•„์š”ํ•˜๋‹ค๋ฉด ์ด๋ฅผ ์žฌ์ •๋ ฌํ•ด ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋™์ผํ•œ ๋ฌธ์„œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.
โ€ข
ํ”Œ๋Ÿฌ๊ทธ์ธ ๋Œ€์‘
์—๋””ํ„ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์€ ๋“ค์–ด์˜ค๋Š” ๊ฐ ๋ณ€ํ™”์— ๋Œ€ํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•˜๊ณ  ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ž์ฒด์ ์ธ ์ƒํƒœ๋ฅผ ์—๋””ํ„ฐ ์ „์ฒด์™€ ๋™๊ธฐํ™”๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋ฌธ์„œ๋ฅผ ์ง์ ‘ ๋ฐ”๊พธ๋Š” ๋ฐฉ์‹์€ ๋‹จ์ˆœํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ณ€ํ™” ๊ณผ์ •์„ ์ถ”์ ํ•  ์ˆ˜ ์—†๊ณ , ๋˜๋Œ๋ฆฌ๊ธฐ๋‚˜ ํ˜‘์—…์ด ์–ด๋ ต๊ณ , ํ”Œ๋Ÿฌ๊ทธ์ธ ํ™•์žฅ์„ฑ๋„ ๋‚ฎ์•„์ง„๋‹ค. ๊ทธ๋ž˜์„œ ProseMirror๋Š” Transform์„ ํ†ตํ•ด ๊ธฐ๋ก ๊ฐ€๋Šฅํ•œ ๋ณ€ํ™” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•จ์œผ๋กœ์จ ๋ณด๋‹ค ๊ฐ•๋ ฅํ•˜๊ณ  ํ™•์žฅ์„ฑ ์žˆ๋Š” ์—๋””ํ„ฐ ์ƒํƒœ๊ณ„๋ฅผ ๋งŒ๋“ ๋‹ค.

Steps

ProseMirror์—์„œ ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ๋Š” Step์ด๋ผ๋Š” ๋‹จ์œ„๋กœ ์ชผ๊ฐœ์–ด ํ‘œํ˜„๋œ๋‹ค. Step์€ ํŠน์ • ํŽธ์ง‘ ์ž‘์—…(์˜ˆ: ์‚ญ์ œ, ๊ต์ฒด, ๋งˆํฌ ์ถ”๊ฐ€ ๋“ฑ)์„ ์ •ํ™•ํžˆ ๊ธฐ์ˆ ํ•˜๋Š” ๊ฐ์ฒด์ด๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ Step์„ ๋‹ค๋ฃฐ ํ•„์š”๋Š” ์—†์ง€๋งŒ, ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€๋Š” ์•Œ์•„๋‘๋ฉด ์œ ์šฉํ•˜๋‹ค.
Step์˜ ์˜ˆ์‹œ
โ€ข
ReplaceStep: ๋ฌธ์„œ์˜ ์ผ๋ถ€๋ถ„์„ ๋‹ค๋ฅธ ๋‚ด์šฉ์œผ๋กœ ๊ต์ฒดํ•œ๋‹ค.
โ€ข
AddMarkStep: ํŠน์ • ๊ตฌ๊ฐ„์— mark(์˜ˆ: bold, italic ๋“ฑ) ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
๊ฐ Step์€ apply ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฌธ์„œ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ ์šฉ ๊ฒฐ๊ณผ๋กœ ์ƒˆ๋กœ์šด ๋ฌธ์„œ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.
console.log(myDoc.toString()) // โ†’ p("hello") // A step that deletes the content between positions 3 and 5 let step = new ReplaceStep(3, 5, Slice.empty) let result = step.apply(myDoc) console.log(result.doc.toString()) // โ†’ p("heo")
JavaScript
๋ณต์‚ฌ

์ค‘์š”ํ•œ ํŠน์ง•

โ€ข
apply()๋Š” ๋‹จ์ˆœํ•œ ์ ์šฉ๋งŒ ํ•œ๋‹ค.
โ—ฆ
๋ฌธ์„œ ๊ตฌ์กฐ(schema)๋ฅผ ๋งž์ถฐ์ฃผ๊ฑฐ๋‚˜, slice๋ฅผ ์ž๋™ ๋ณด์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.
โ—ฆ
์ฆ‰, ๊ทœ์น™์— ์–ด๊ธ‹๋‚˜๋Š” Step์€ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋…ธ๋“œ์˜ ์‹œ์ž‘ ํ† ํฐ๋งŒ ์‚ญ์ œํ•˜๋ ค ํ•˜๋ฉด ๋‹ซ๋Š” ํ† ํฐ์ด ๋‚จ์•„ ๋„ํ๋จผํŠธ๊ฐ€ ๋ถˆ๊ท ํ˜•ํ•ด์ง€๊ณ  ๋ฌดํšจ ์ƒํƒœ๊ฐ€ ๋˜์–ด ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ apply()๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฐ๊ณผ ๊ฐ์ฒด(StepResult) ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด ๊ฐ์ฒด๋Š” ์„ฑ๊ณต ์‹œ ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ, ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ด๋Š”๋‹ค.

์‹ค๋ฌด์—์„œ๋Š”?

์ง์ ‘ Step์„ ๋งŒ๋“ค๊ธฐ๋ณด๋‹ค๋Š”, Transform.replace ๊ฐ™์€ ํ—ฌํผ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด Step์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์ผ๋ฐ˜์ ์ด๋‹ค. ์ด๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ๋งŒ์กฑํ•˜๋„๋ก, ๋‚ด๋ถ€์ ์œผ๋กœ ์•ˆ์ „ํ•œ Step๋งŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.
์š”์•ฝํ•˜์ž๋ฉด,
โ€ข
Step์€ ๋ฌธ์„œ ๋ณ€๊ฒฝ์˜ ์ตœ์†Œ ๋‹จ์œ„
โ€ข
apply()๋Š” ์ƒˆ๋กœ์šด ๋„ํ๋จผํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•จ
โ€ข
์ง์ ‘ ๋‹ค๋ฃจ๊ธฐ๋ณด๋‹จ Transform์˜ helper๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Transforms

ํŽธ์ง‘ ๋™์ž‘(editing action)์€ ํ•˜๋‚˜ ์ด์ƒ์˜ Step์„ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ step์„ ์—ฐ์†์ ์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ ๊ฐ€์žฅ ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์€ Transform ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. (๋˜๋Š” ์ „์ฒด ์—๋””ํ„ฐ ์ƒํƒœ์™€ ํ•จ๊ป˜ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด, Transform์„ ์ƒ์†ํ•œ Transaction์„ ์‚ฌ์šฉํ•œ๋‹ค.)
let tr = new Transform(myDoc) tr.delete(5, 7) // Delete between position 5 and 7 tr.split(5) // Split the parent node at position 5 console.log(tr.doc.toString()) // The modified document console.log(tr.steps.length) // โ†’ 2
JavaScript
๋ณต์‚ฌ
Transform์˜ ๋Œ€๋ถ€๋ถ„์˜ ๋ฉ”์„œ๋“œ๋Š” ์ž๊ธฐ ์ž์‹ (this) ์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ tr.delete(5, 7).split(5) ๊ฐ™์ด ์ฒด์ด๋‹ ๋ฐฉ์‹์œผ๋กœ ์—ฐ๋‹ฌ์•„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์–‘ํ•œ Transform ๋ฉ”์„œ๋“œ๋“ค

โ€ข
delete / replace: ์‚ญ์ œ, ๊ต์ฒด
โ€ข
addMark / removeMark: ๋งˆํฌ ์ถ”๊ฐ€/์ œ๊ฑฐ
โ€ข
split: ๋…ธ๋“œ ๋ถ„๋ฆฌ
โ€ข
join: ์ธ์ ‘ ๋…ธ๋“œ ๋ณ‘ํ•ฉ
โ€ข
lift: ํŠน์ • ๋…ธ๋“œ๋ฅผ ์ƒ์œ„๋กœ ์˜ฌ๋ฆผ
โ€ข
wrap: ํŠน์ • ๋…ธ๋“œ๋ฅผ ์ƒˆ๋กœ์šด ๋ž˜ํผ๋กœ ๊ฐ์Œˆ
๋“ฑ ๋‹ค์–‘ํ•œ ๋ฌธ์„œ ๊ตฌ์กฐ ๋ณ€ํ˜•์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Mapping

๋„ํ๋จผํŠธ์— ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋ฉด, ํ•ด๋‹น ๋„ํ๋จผํŠธ์˜ ํŠน์ • ์œ„์น˜๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋˜ ์ขŒํ‘œ(position)๋“ค์ด ๋ฌดํšจํ™”๋˜๊ฑฐ๋‚˜ ์˜๋ฏธ๊ฐ€ ๋ฐ”๋€” ์ˆ˜ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด,
โ€ข
๋„ํ๋จผํŠธ์— ๊ธ€์ž๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด, ๊ทธ ์ดํ›„์˜ ๋ชจ๋“  ์œ„์น˜๋“ค์€ ํ•œ ๊ธ€์ž ์•ž์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋จ
โ€ข
๋„ํ๋จผํŠธ ์ „์ฒด๊ฐ€ ์‚ญ์ œ๋˜๋ฉด, ํ•ด๋‹น ์œ„์น˜๋“ค์„ ๊ฐ€๋ฆฌํ‚ค๋˜ ํฌ์ง€์…˜์€ ๋” ์ด์ƒ ์œ ํšจํ•˜์ง€ ์•Š์Œ
ํ•˜์ง€๋งŒ ์‹ค์ œ ์—๋””ํ„ฐ์—์„œ๋Š” ์„ ํƒ ์˜์—ญ(selection boundaries) ๊ฐ™์€ ์ขŒํ‘œ ์ •๋ณด๋ฅผ ๋ณ€๊ฒฝ ์ดํ›„์—๋„ ์œ ์ง€ํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด, ๊ฐ Step์€ StepMap์ด๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝ ์ „/ํ›„ ํฌ์ง€์…˜ ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ œ๊ณตํ•œ๋‹ค.
let step = new ReplaceStep(4, 6, Slice.empty) // Delete 4-5 let map = step.getMap() console.log(map.map(8)) // โ†’ 6 (2ํ† ํฐ ๋น ์กŒ์œผ๋‹ˆ 8 โ†’ 6) console.log(map.map(2)) // โ†’ 2 (์‚ญ์ œ ์ด์ „ ์œ„์น˜๋ผ ๋ณ€ํ™” ์—†์Œ)
JavaScript
๋ณต์‚ฌ
Transform ๊ฐ์ฒด๋Š” ์—ฌ๋Ÿฌ Step์ด ์—ฐ์†๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๊ฐ Step์˜ map์„ ์ž๋™์œผ๋กœ ๋ˆ„์ ํ•ด์„œ Mapping์ด๋ผ๋Š” ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.
let tr = new Transform(myDoc) tr.split(10) // 10์—์„œ ๋…ธ๋“œ ๋ถ„๋ฆฌ โ†’ ํ† ํฐ 2๊ฐœ ์ถ”๊ฐ€๋จ tr.delete(2, 5) // 2~4 ์‚ญ์ œ โ†’ ํ† ํฐ 3๊ฐœ ์‚ญ์ œ๋จ console.log(tr.mapping.map(15)) // โ†’ 14 console.log(tr.mapping.map(6)) // โ†’ 3 console.log(tr.mapping.map(10)) // โ†’ 9
JavaScript
๋ณต์‚ฌ

๊ฒฝ๊ณ„์—์„œ์˜ ์œ„์น˜ ๋งคํ•‘์€ ๋ชจํ˜ธํ•  ์ˆ˜ ์žˆ๋‹ค

์˜ˆ๋ฅผ ๋“ค์–ด, split(10)์€ ํฌ์ง€์…˜ 10์—์„œ ํ† ํฐ 2๊ฐœ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š”๋ฐ, ์ด๋•Œ 10 ํฌ์ง€์…˜์„ ๋งคํ•‘ํ•˜๋ฉด ์‚ฝ์ž…๋œ ๋’ค๋กœ ์ด๋™ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๊ธฐ์กด ์œ„์น˜ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ธฐ๋ณธ์€ ์‚ฝ์ž… ๋’ค๋กœ ์ด๋™ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ๋Š” split์„ ํ•˜๋ฉฐ ํ† ํฐ 2๊ฐœ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์ง€๋งŒ delete๋กœ ์ธํ•ด ํ† ํฐ 3๊ฐœ๊ฐ€ ์‚ญ์ œ๋˜์–ด 10 ํฌ์ง€์…˜์ด -1 ์ด๋™ํ•˜์—ฌ 9๋กœ ๋งคํ•‘๋œ ๊ฒƒ์ด๋‹ค.
console.log(tr.mapping.map(10)) // โ†’ 9 (์‚ฝ์ž… ๋’ค๋กœ ์ด๋™)
JavaScript
๋ณต์‚ฌ
.map(pos, -1)์ฒ˜๋Ÿผ bias ๊ฐ’์„ -1๋กœ ์ฃผ๋ฉด, ๊ธฐ์กด ์œ„์น˜ ์œ ์ง€ ์ชฝ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. ์ฆ‰, split์œผ๋กœ ์ธํ•œ ํ† ํฐ ์‚ฝ์ž… ์ „์œผ๋กœ ์œ„์น˜๋ฅผ ๊ฐ„์ฃผํ•˜์—ฌ ๊ธฐ์กด ์œ„์น˜์—์„œ delete token 3ํ•œ ๊ฐ’, 10 - 3์„ ํ•˜์—ฌ 7 ํฌ์ง€์…˜์ด ๋œ ๊ฒƒ์ด๋‹ค.
console.log(tr.mapping.map(10, -1)) // โ†’ 7
JavaScript
๋ณต์‚ฌ
์™œ ์ด๋Ÿฐ ๊ตฌ์กฐ๋กœ ๋งŒ๋“ค์—ˆ๋Š”๊ฐ€?
๊ฐ Step์„ ์ž‘๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜ํ•˜๋ฉด, ํฌ์ง€์…˜ ๋งคํ•‘(position mapping), Step ๋˜๋Œ๋ฆฌ๊ธฐ (undo), ์„œ๋กœ ๋‹ค๋ฅธ Step ๊ฐ„์˜ ์œ„์น˜ ๋ณด์ • ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ •ํ™•ํ•˜๊ณ  ์†์‹ค ์—†์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

Rebasing

Rebase๋ž€, ๋‘ ๊ฐœ์˜ ๋ณ€๊ฒฝ(step)์ด ๋™์ผํ•œ ๋„ํ๋จผํŠธ์—์„œ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ, ํ•œ ์ชฝ์ด ๋จผ์ € ์ ์šฉ๋œ ๋„ํ๋จผํŠธ ์œ„์— ๋‚˜๋จธ์ง€ ๋ณ€๊ฒฝ์„ ๋‹ค์‹œ ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ์ž‘์—…์ด๋‹ค. ํ˜‘์—… ํŽธ์ง‘์ด๋‚˜ ๋ณ€๊ฒฝ ์ถ”์ (change tracking) ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ ค ํ•  ๋•Œ ๊ผญ ํ•„์š”ํ•œ ๊ฐœ๋…์ด๋‹ค.

๋‹จ์ผ Step

์ด ์ƒํƒœ์—์„œ
stepA(doc) = docA stepB(doc) = docB
JavaScript
๋ณต์‚ฌ
๊ฐ™์€ ๋„ํ๋จผํŠธ์— A, B ๋ผ๋Š” ์Šคํ…์„ ์ ์šฉํ–ˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.
stepB(docA) = โŒ // ์—๋Ÿฌ. docA๋Š” stepB์˜ ๋Œ€์ƒ๊ณผ ๋‹ค๋ฆ„
JavaScript
๋ณต์‚ฌ
docA์— ๋Œ€ํ•ด stepB๋ฅผ ์ ์šฉํ•˜๋ฉด docA๋Š” stepB์˜ ๋Œ€์ƒ ๋„ํ๋จผํŠธ์™€ ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค.
์ด๋•Œ stepB๋ฅผ docA ๊ธฐ์ค€์œผ๋กœ โ€œ์žฌ์ •๋ ฌโ€ํ•ด์•ผ ํ•œ๋‹ค.
rebase(stepB, mapA) = stepB' stepB'(docA) = docAB
JavaScript
๋ณต์‚ฌ
์—ฌ๊ธฐ์„œ mapA๋Š” stepA.getMap() ๊ฐ™์€ ๊ฑฐ๊ณ , stepB.map(mapA)๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด stepB'๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ค‘์ฒฉ๋œ Step์„ Rebaseํ•  ๋•Œ

stepA2(stepA1(doc)) = docA stepB2(stepB1(doc)) = docB
JavaScript
๋ณต์‚ฌ
์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฑด
???(docA) = docAB
JavaScript
๋ณต์‚ฌ
์ฆ‰, stepA1 + stepA2๊ฐ€ ์ ์šฉ๋œ ๋ฌธ์„œ(docA)์— stepB1 + stepB2์˜ ํšจ๊ณผ๊นŒ์ง€ ์ ์šฉํ•ด์„œ docAB๋ฅผ ์–ป๊ณ  ์‹ถ์€ ๊ฒƒ.
Step๋“ค์„ ์–ด๋–ป๊ฒŒ Rebaseํ• ๊นŒ?
โ€ข
stepB1์€ stepA1, stepA2์˜ map์„ ํ†ตํ•ด ์‰ฝ๊ฒŒ ๋ณ€ํ™˜ ๊ฐ€๋Šฅ โ†’ stepB1'
โ€ข
ํ•˜์ง€๋งŒ stepB2๋Š” stepB1'์ด ์•„๋‹Œ stepB1 ์ดํ›„ ์ƒํƒœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋” ๋ณต์žกํ•ด์ง„๋‹ค.
๊ทธ๋ž˜์„œ stepB2๋ฅผ Rebaseํ•˜๋ ค๋ฉด?
์ด ๊ฒฝ์šฐ, stepB2๋Š” ์•„๋ž˜์˜ ์—ฐ๊ฒฐ๋œ ๋งต ์ฒด์ธ์„ ๋”ฐ๋ผ์•ผ ํ•œ๋‹ค.
rebase(stepB2, [ invert(mapB1), // stepB1์ด ์‚ฝ์ž…ํ•œ ์œ„์น˜ ๊ธฐ์ค€ โ†’ ์›๋ž˜ ๋ฌธ์„œ ๊ธฐ์ค€์œผ๋กœ ๋˜๋Œ๋ฆฌ๊ธฐ mapA1, mapA2, // stepA ๋“ค์˜ ๋ณ€ํ™” ์ ์šฉ mapB1' // ์žฌ๋ฐฐ์น˜๋œ stepB1์˜ map ])
JavaScript
๋ณต์‚ฌ
โ€ข
๋จผ์ € stepB2๊ฐ€ ์ฐธ์กฐํ•˜๋˜ ์œ„์น˜๋ฅผ ์›๋ž˜ ์œ„์น˜๋กœ ๋ณต์›ํ•˜๊ณ 
โ€ข
๊ทธ ์›๋ž˜ ์œ„์น˜๋ฅผ stepA1 + stepA2๋ฅผ ๊ฑฐ์ณ ๋ณ€ํ™”๋œ ๋ฌธ์„œ๋กœ ๋ณ€ํ™˜
โ€ข
๋งˆ์ง€๋ง‰์œผ๋กœ, stepB1'์— ์˜ํ•ด ๋‹ค์‹œ ์‚ฝ์ž…๋œ ์œ„์น˜๋ฅผ ๋ฐ˜์˜
์กฐ๊ธˆ ๋ณต์žกํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด ์ข€ ๋” ์‰ฝ๊ฒŒ ์ดํ•ดํ•ด๋ณด์ž.
doc์—์„œ ๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋‹ค๋ฅธ ์ž‘์—…์„ ํ–ˆ๋‹ค.
โ€ข
A ์‚ฌ์šฉ์ž๋Š” ๊ธ€ ๋จธ๋ฆฌ์—์„œ ์ œ๋ชฉ์„ ๋ถ™์˜€๋‹ค.
โ€ข
B ์‚ฌ์šฉ์ž๋Š” ์ค‘๊ฐ„์— ์žˆ๋Š” ๋ฌธ์žฅ ํ•˜๋‚˜๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค.
// ์›๋ณธ ๋ฌธ์„œ doc = "์˜ค๋Š˜ ๋‚ ์”จ๋Š” ์ข‹๋‹ค. ์‚ฐ์ฑ…์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค."
JavaScript
๋ณต์‚ฌ
์‚ฌ์šฉ์ž A์˜ ๋ณ€๊ฒฝ (stepA1, stepA2)
// stepA1: ์•ž์— "์ œ๋ชฉ: "์„ ์‚ฝ์ž… โ†’ "์ œ๋ชฉ: ์˜ค๋Š˜ ๋‚ ์”จ๋Š” ์ข‹๋‹ค. ์‚ฐ์ฑ…์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค." // stepA2: ๋งˆ์ง€๋ง‰ ๋ฌธ์žฅ์„ "๋“ฑ์‚ฐ์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค."๋กœ ๋ณ€๊ฒฝ โ†’ "์ œ๋ชฉ: ์˜ค๋Š˜ ๋‚ ์”จ๋Š” ์ข‹๋‹ค. ๋“ฑ์‚ฐ์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค."
JavaScript
๋ณต์‚ฌ
์‚ฌ์šฉ์ž B์˜ ๋ณ€๊ฒฝ (stepB1, stepB2)
// stepB1: "๋‚ ์”จ๋Š” ์ข‹๋‹ค"๋ฅผ "๋‚ ์”จ๊ฐ€ ๋ง‘๋‹ค"๋กœ ๋ณ€๊ฒฝ โ†’ "์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ๋ง‘๋‹ค. ์‚ฐ์ฑ…์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค." // stepB2: "์‚ฐ์ฑ…"์ด๋ผ๋Š” ๋‹จ์–ด์— ๋ฐ‘์ค„(mark)์„ ์ถ”๊ฐ€ โ†’ "์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ๋ง‘๋‹ค. _์‚ฐ์ฑ…_์„ ๊ฐ€๊ณ  ์‹ถ๋‹ค."
JavaScript
๋ณต์‚ฌ
์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š”, docA = stepA2(stepA1(doc))์ด ์ ์šฉ๋œ ์ƒํƒœ์ธ๋ฐ, stepB1, stepB2๋Š” ์›๋ณธ ๋ฌธ์„œ ๊ธฐ์ค€์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๊ณ  ๋•Œ๋ฌธ์—docA์—๋Š” ์ ์šฉ์ด ์•ˆ๋œ๋‹ค. ๊ทธ๋ž˜์„œ rebase๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ด๋‹ค.
rebase(stepB2, [ invert(mapB1), // stepB1์ด ์‚ฝ์ž…ํ–ˆ๋˜ ์œ„์น˜๋ฅผ "๋˜๋Œ๋ฆผ" โ€” ์›๋ž˜ ์œ„์น˜๋กœ ๊ฐ mapA1, mapA2, // A ์‚ฌ์šฉ์ž์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•ด์„œ ์œ„์น˜ ๋‹ค์‹œ ์ด๋™ mapB1' // stepB1์„ docA ๊ธฐ์ค€์œผ๋กœ ๋‹ค์‹œ ์ ์šฉํ•œ ๋ฒ„์ „์˜ map ])
JavaScript
๋ณต์‚ฌ
1.
invert(mapB1): stepB2๊ฐ€ ๊ธฐ์ค€์œผ๋กœ ์‚ผ๋˜ stepB1 ์ ์šฉ ํ›„ ๋ฌธ์„œ๋ฅผ ๋‹ค์‹œ ์›๋ž˜ ๋ฌธ์„œ ๊ธฐ์ค€์˜ ์œ„์น˜๋กœ ๋˜๋Œ๋ฆผ
2.
mapA1, mapA2: ์›๋ž˜ ๋ฌธ์„œ ๊ธฐ์ค€ ์œ„์น˜๋ฅผ A ์‚ฌ์šฉ์ž์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•œ ์ƒˆ๋กœ์šด ๋ฌธ์„œ docA ์œ„์น˜๋กœ ์ด๋™์‹œํ‚ด
3.
mapB1โ€™: ์ด์ œ ์ด ์œ„์น˜์—์„œ stepB1์ด ๋‹ค์‹œ ์‚ฝ์ž…๋œ ์œ„์น˜๋ฅผ ๋ฐ˜์˜ํ•ด์ค˜์•ผ stepB2๊ฐ€ ์ •ํ™•ํ•œ ๋Œ€์ƒ์— ๋งˆํฌ๋ฅผ ๋‹ค์‹œ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Œ
์ฆ‰, ์ •๋ฆฌํ•˜๋ฉด ์œ„์˜ ์ฝ”๋“œ๋Š” stepB2๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด, stepB1์˜ ์œ„์น˜๋ฅผ ๋˜๋Œ๋ ค์„œ ์›๋ณธ doc์˜ ์œ„์น˜๋ฅผ ์•Œ์•„๋‚ด๊ณ , ์—ฌ๊ธฐ์— stepA1, stepA2์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ ์šฉํ•ด์„œ docA์˜ ์œ„์น˜๋ฅผ ์•Œ์•„๋‚ธ ํ›„, docA๋ฅผ ๊ธฐ์ค€์œผ๋กœ stepB1 ์ ์šฉํ•œ ๋ฒ„์ „์˜ map์— stepB2๋ฅผ ์ ์šฉํ•œ๋‹ค.
โ€ข
stepB2๋Š” "์‚ฐ์ฑ…"์ด๋ผ๋Š” ๋‹จ์–ด์— ๋ฐ‘์ค„์„ ๊ธ‹๋Š” ๋™์ž‘
โ€ข
๊ทผ๋ฐ "์‚ฐ์ฑ…"์˜ ์œ„์น˜๋Š” A์˜ ๋ณ€๊ฒฝ, B์˜ ์‚ฝ์ž…์— ๋”ฐ๋ผ ๊ณ„์† ์ด๋™
โ€ข
๊ทธ ์ด๋™ ํ๋ฆ„์„ "๋˜๋Œ๋ ธ๋‹ค โ†’ A์˜ ๋ณ€๊ฒฝ ์ ์šฉ โ†’ B์˜ ์‚ฝ์ž… ๋ฐ˜์˜" ์ˆœ์„œ๋กœ ๋”ฐ๋ผ๊ฐ€, ํ˜„์žฌ ๋„ํ๋จผํŠธ์—์„œ "์‚ฐ์ฑ…"์ด ์–ด๋”” ์žˆ๋Š”์ง€๋ฅผ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•œ ํ›„ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋ณต์žกํ•œ ์ƒํ™ฉ: ์‚ฝ์ž…๋œ ์ฝ˜ํ…์ธ ๋ฅผ ๋‹ค์‹œ ์ฐธ์กฐํ•  ๋•Œ

์˜ˆ๋ฅผ ๋“ค์–ด, stepB1์ด ๋ฌธ์ž์—ด์„ ์‚ฝ์ž…ํ•˜๊ณ  stepB2๊ฐ€ ๊ทธ ์‚ฝ์ž…๋œ ๋ฌธ์ž์—ด์— ๋งˆํฌ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.
์ด๋Ÿฐ ๊ฒฝ์šฐ,invert(mapB1)์„ ํ†ตํ•ด ์œ„์น˜๋ฅผ ๋˜๋Œ๋ฆฌ๋ฉด, ๊ทธ ๋ฌธ์ž์—ด์€ ์•„์ง ๋ฌธ์„œ์— ์—†์œผ๋ฏ€๋กœ stepB2.map(...) === null ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฝ˜ํ…์ธ ๋Š” mapB1'์— ์˜ํ•ด ๋‹ค์‹œ ์‚ฝ์ž…๋˜๊ธฐ ๋•Œ๋ฌธ์—, Mapping์„ ํ†ตํ•ด ๊ทธ ๊ด€๊ณ„๋ฅผ ์ถ”์ ํ•ด์•ผ ํ•œ๋‹ค. (ProseMirror์˜ Mapping ๊ฐ์ฒด๋Š” ์ด๋Ÿฐ ๋ณต์žกํ•œ ์—ฐ๊ฒฐ ๊ตฌ์กฐ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.)

์žฌ๋ฐฐ์น˜ํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ๋ฌด์กฐ๊ฑด ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฑด ์•„๋‹ˆ๋‹ค

์˜ˆ๋ฅผ ๋“ค์–ด, ์–ด๋–ค step์ด ํŠน์ • ์œ„์น˜์— mark๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค ํ–ˆ๋Š”๋ฐ, ๊ทธ ๋ถ€๋ชจ ๋…ธ๋“œ๊ฐ€ ์ด์ œ mark๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ฒŒ ๋๋‹ค๋ฉด step.apply()๋Š” ์‹คํŒจํ•˜๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋ณดํ†ต ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ๊ทธ๋ƒฅ ํ•ด๋‹น step์„ ๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜๋‹ค.

์šฉ์–ด ์ •๋ฆฌ

์šฉ์–ด
์„ค๋ช…
Step
๋ฌธ์„œ์— ์ ์šฉ๋˜๋Š” ๋‹จ์ผ ๋ณ€๊ฒฝ
StepMap
step์ด ๋ฌธ์„œ์—์„œ ์œ„์น˜๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝํ–ˆ๋Š”์ง€ ๊ธฐ๋กํ•œ ๊ฐ์ฒด
Mapping
์—ฌ๋Ÿฌ StepMap์„ ์—ฐ๊ฒฐํ•ด ํฌ์ง€์…˜์„ ํ•œ๊บผ๋ฒˆ์— ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ
invert()
step์˜ ๋ฐ˜๋Œ€ ๋™์ž‘ (์‚ญ์ œ โ†” ์‚ฝ์ž… ๋“ฑ)
rebase()
ํ•œ step์„, ๋‹ค๋ฅธ step ์ดํ›„์—๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก โ€œ๋‹ค์‹œ ๋งคํ•‘โ€ํ•˜๋Š” ๊ณผ์ •

Commands

ProseMirror์—์„œ ๋ช…๋ น์–ด(command)๋ž€ ์‚ฌ์šฉ์ž๊ฐ€ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค๋‚˜ ๋ฉ”๋‰ด ์ธํ„ฐ๋ž™์…˜์„ ํ†ตํ•ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํŽธ์ง‘ ๋™์ž‘์„ ์ •์˜ํ•œ ํ•จ์ˆ˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ…์ŠคํŠธ๋ฅผ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜, ๋ธ”๋ก์„ ๋ถ„ํ• ํ•˜๊ฑฐ๋‚˜, ํฌ๋งท์„ ์ ์šฉํ•˜๋Š” ๋“ฑ์˜ ๋ชจ๋“  ํ–‰์œ„๋Š” ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

Command ํ•จ์ˆ˜์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ

ProseMirror command๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์˜ ํ•จ์ˆ˜๋กœ ์ž‘์„ฑํ•œ๋‹ค.
function myCommand(state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView): boolean
TypeScript
๋ณต์‚ฌ
โ€ข
state: ํ˜„์žฌ ์—๋””ํ„ฐ์˜ ์ƒํƒœ (ํ•„์ˆ˜)
โ€ข
dispatch: ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ (์„ ํƒ)
โ€ข
view: DOM์— ์ ‘๊ทผํ•ด์•ผ ํ•˜๋Š” ๋ช…๋ น์–ด์—์„œ๋งŒ ์‚ฌ์šฉ (์„ ํƒ)
์˜ˆ์‹œ: ์„ ํƒ๋œ ํ…์ŠคํŠธ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ช…๋ น์–ด
function deleteSelection(state, dispatch) { if (state.selection.empty) return false if (dispatch) dispatch(state.tr.deleteSelection()) return true }
TypeScript
๋ณต์‚ฌ
โ€ข
์„ ํƒ ์˜์—ญ์ด ๋น„์–ด ์žˆ์œผ๋ฉด ์•„๋ฌด ๋™์ž‘๋„ ํ•˜์ง€ ์•Š๊ณ  false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
โ€ข
์„ ํƒ ์˜์—ญ์ด ์žˆ๋‹ค๋ฉด, ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•ด dispatchํ•˜๊ณ  true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

dispatch ์ธ์ž๋ฅผ ์ƒ๋žตํ•˜๋Š” ์ด์œ 

command๋Š” ์‹ค์ œ๋กœ ์‹คํ–‰๋˜๊ธฐ๋„ ํ•˜์ง€๋งŒ, ์‹คํ–‰ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋งŒ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœ๋  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ dispatch ์ธ์ž๋ฅผ ์ƒ๋žตํ•˜๋ฉด ๋ช…๋ น์–ด๋Š” ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š๊ณ  true/false๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
์˜ˆ์‹œ
// ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ์ง€๋งŒ ํ™•์ธ deleteSelection(state) // ์‹ค์ œ๋กœ ์‹คํ–‰ deleteSelection(state, dispatch)
TypeScript
๋ณต์‚ฌ
์ด๋Ÿฌํ•œ ๊ตฌ์กฐ ๋•๋ถ„์— ๋ฉ”๋‰ด๋ฐ”๋‚˜ ํˆด๋ฐ”๋Š” ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ๋ฒ„ํŠผ์„ ํ™œ์„ฑ/๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ช…๋ น์–ด์—์„œ view๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

๋Œ€๋ถ€๋ถ„์˜ ๋ช…๋ น์–ด๋Š” state์™€ dispatch๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ์ผ๋ถ€๋Š” DOM ์œ„์น˜๋‚˜ ํฌ์ปค์Šค๋ฅผ ํ™•์ธํ•ด์•ผ ํ•˜๋ฏ€๋กœ view๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค.
์˜ˆ์‹œ
function blinkView(_state, dispatch, view) { if (dispatch) { view.dom.style.background = "yellow" setTimeout(() => (view.dom.style.background = ""), 1000) } return true }
TypeScript
๋ณต์‚ฌ
์ด ๋ช…๋ น์–ด๋Š” ์‹คํ–‰ ์‹œ ์—๋””ํ„ฐ DOM์˜ ๋ฐฐ๊ฒฝ์„ 1์ดˆ๊ฐ„ ๋…ธ๋ž€์ƒ‰์œผ๋กœ ๋ฐ”๊ฟจ๋‹ค๊ฐ€ ๋ณต์›ํ•œ๋‹ค. ์‹ค๋ฌด์—์„œ๋Š” ์ด์™€ ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๋„์šฐ๊ฑฐ๋‚˜ ํฌ์ปค์Šค๋ฅผ ์ด๋™ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์—ฌ๋Ÿฌ ๋ช…๋ น์–ด๋ฅผ ์ฒด์ด๋‹ํ•˜๊ธฐ: chainCommands

์—ฌ๋Ÿฌ ๋ช…๋ น์–ด๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ ์‹œ๋„ํ•˜๊ณ , ๊ฐ€์žฅ ๋จผ์ € ์„ฑ๊ณตํ•œ ๋ช…๋ น์–ด๋งŒ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
import { chainCommands, deleteSelection, joinBackward, selectNodeBackward } from 'prosemirror-commands' const backspaceCommand = chainCommands( deleteSelection, joinBackward, selectNodeBackward )
TypeScript
๋ณต์‚ฌ
์ด ์˜ˆ์‹œ์—์„œ deleteSelection์ด ๋จผ์ € ์‹คํ–‰๋˜๋ฉฐ, ๊ทธ๊ฒŒ ์‹คํŒจํ•˜๋ฉด joinBackward, ๊ทธ๊ฒƒ๋„ ์‹คํŒจํ•˜๋ฉด selectNodeBackward๊ฐ€ ์‹œ๋„๋œ๋‹ค. ์„ธ ๊ฐ€์ง€ ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ๋™์ž‘(Backspace)์ด ์‹คํ–‰๋œ๋‹ค.

์ž์ฃผ ์“ฐ๋Š” ๋ช…๋ น์–ด๋“ค

ProseMirror๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ๋ช…๋ น์–ด ์ƒ์„ฑ๊ธฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค:
โ€ข
toggleMark(markType): ๋งˆํฌ ์˜จ/์˜คํ”„ (ex. Bold, Italic)
โ€ข
setBlockType(nodeType): ๋ธ”๋ก ๋…ธ๋“œ ์œ ํ˜• ๋ณ€๊ฒฝ (ex. Heading โ†’ Paragraph)
โ€ข
wrapIn(nodeType): ํŠน์ • ๋…ธ๋“œ๋กœ ๊ฐ์‹ธ๊ธฐ
โ€ข
undo, redo: ํžˆ์Šคํ† ๋ฆฌ ํŠธ๋žœ์žญ์…˜ ๋˜๋Œ๋ฆฌ๊ธฐ/์žฌ์‹คํ–‰
โ€ข
baseKeymap: ์œ„ ๋ช…๋ น์–ด๋“ค์„ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค์— ์—ฐ๊ฒฐํ•œ ๊ธฐ๋ณธ ํ‚ค๋งต

์ปค์Šคํ…€ Commands๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

ProseMirror๋Š” schema์— ๋”ฐ๋ผ ๋™์ž‘์ด ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž ์ •์˜ ๋…ธ๋“œ๋ฅผ ํŽธ์ง‘ํ•˜๊ฑฐ๋‚˜, ์‚ฌ์šฉ์ž ๋งž์ถค ํ–‰๋™์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ ์ง์ ‘ command๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์ปค์Šคํ…€ command๋„ ์œ„์—์„œ ์†Œ๊ฐœํ•œ ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๋ฉด ๋œ๋‹ค.
ProseMirror์˜ ๋ช…๋ น์–ด ์‹œ์Šคํ…œ์€ ์ง๊ด€์ ์ด์ง€๋Š” ์•Š์ง€๋งŒ, ์ผ๋‹จ ์ต์ˆ™ํ•ด์ง€๋ฉด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์˜ ์ž์œ ๋„๊ฐ€ ๋งค์šฐ ๋†’๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ์‹ค์ œ๋กœ ์ปค๋งจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์กฐ๊ธˆ์”ฉ ๊ฐ์„ ์ตํ˜€๊ฐ€๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋น ๋ฅธ ํ•™์Šต๋ฒ•์ด๋‹ค.
์ง€๊ธˆ๊นŒ์ง€ ํ”„๋กœ์Šค๋ฏธ๋Ÿฌ ๊ณต์‹๋ฌธ์„œ์˜ ๊ฐ€์ด๋“œ๋ฅผ ๋ชจ๋‘ ์‚ดํŽด๋ณด์•˜๋‹ค. ๋‹ค์Œ์€ Prosemirror๋กœ ์—๋””ํ„ฐ ๋งŒ๋“ค๋ฉด์„œ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๊ฐœ๋… ์ ์šฉํ•ด๋ณด๊ฒ ๋‹ค.

References