@moda-editor/core 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/dist/collab.d.ts +61 -0
  2. package/dist/collab.d.ts.map +1 -0
  3. package/dist/collab.js +67 -0
  4. package/dist/collab.js.map +1 -0
  5. package/dist/commands.d.ts +11 -0
  6. package/dist/commands.d.ts.map +1 -1
  7. package/dist/commands.js +48 -4
  8. package/dist/commands.js.map +1 -1
  9. package/dist/conformance/editor-base.d.ts +7 -0
  10. package/dist/conformance/editor-base.d.ts.map +1 -0
  11. package/dist/conformance/editor-base.js +304 -0
  12. package/dist/conformance/editor-base.js.map +1 -0
  13. package/dist/conformance/editor-codeblock.d.ts +8 -0
  14. package/dist/conformance/editor-codeblock.d.ts.map +1 -0
  15. package/dist/conformance/editor-codeblock.js +402 -0
  16. package/dist/conformance/editor-codeblock.js.map +1 -0
  17. package/dist/conformance/editor-ime.d.ts +7 -0
  18. package/dist/conformance/editor-ime.d.ts.map +1 -0
  19. package/dist/conformance/editor-ime.js +166 -0
  20. package/dist/conformance/editor-ime.js.map +1 -0
  21. package/dist/conformance/editor-input.d.ts +8 -0
  22. package/dist/conformance/editor-input.d.ts.map +1 -0
  23. package/dist/conformance/editor-input.js +676 -0
  24. package/dist/conformance/editor-input.js.map +1 -0
  25. package/dist/conformance/editor-nodeview.d.ts +7 -0
  26. package/dist/conformance/editor-nodeview.d.ts.map +1 -0
  27. package/dist/conformance/editor-nodeview.js +169 -0
  28. package/dist/conformance/editor-nodeview.js.map +1 -0
  29. package/dist/conformance/editor-overlay.d.ts +8 -0
  30. package/dist/conformance/editor-overlay.d.ts.map +1 -0
  31. package/dist/conformance/editor-overlay.js +345 -0
  32. package/dist/conformance/editor-overlay.js.map +1 -0
  33. package/dist/conformance/fixtures.d.ts +4 -0
  34. package/dist/conformance/fixtures.d.ts.map +1 -0
  35. package/dist/conformance/fixtures.js +15 -0
  36. package/dist/conformance/fixtures.js.map +1 -0
  37. package/dist/conformance/index.d.ts +14 -0
  38. package/dist/conformance/index.d.ts.map +1 -0
  39. package/dist/conformance/index.js +28 -0
  40. package/dist/conformance/index.js.map +1 -0
  41. package/dist/conformance/types.d.ts +51 -0
  42. package/dist/conformance/types.d.ts.map +1 -0
  43. package/dist/conformance/types.js +2 -0
  44. package/dist/conformance/types.js.map +1 -0
  45. package/dist/conformance/widget.d.ts +3 -0
  46. package/dist/conformance/widget.d.ts.map +1 -0
  47. package/dist/conformance/widget.js +49 -0
  48. package/dist/conformance/widget.js.map +1 -0
  49. package/dist/conformance.d.ts +1 -58
  50. package/dist/conformance.d.ts.map +1 -1
  51. package/dist/conformance.js +3 -1748
  52. package/dist/conformance.js.map +1 -1
  53. package/dist/editor/editor.d.ts.map +1 -1
  54. package/dist/editor/editor.js +18 -1
  55. package/dist/editor/editor.js.map +1 -1
  56. package/dist/editor/extension.d.ts +47 -3
  57. package/dist/editor/extension.d.ts.map +1 -1
  58. package/dist/features/ace.d.ts +4 -0
  59. package/dist/features/ace.d.ts.map +1 -1
  60. package/dist/features/ace.js +59 -20
  61. package/dist/features/ace.js.map +1 -1
  62. package/dist/features/block-dnd.d.ts +7 -0
  63. package/dist/features/block-dnd.d.ts.map +1 -0
  64. package/dist/features/block-dnd.js +207 -0
  65. package/dist/features/block-dnd.js.map +1 -0
  66. package/dist/features/code-block.d.ts +9 -84
  67. package/dist/features/code-block.d.ts.map +1 -1
  68. package/dist/features/code-block.js +84 -1034
  69. package/dist/features/code-block.js.map +1 -1
  70. package/dist/features/code-context.d.ts +81 -0
  71. package/dist/features/code-context.d.ts.map +1 -0
  72. package/dist/features/code-context.js +322 -0
  73. package/dist/features/code-context.js.map +1 -0
  74. package/dist/features/code-edit.d.ts +50 -0
  75. package/dist/features/code-edit.d.ts.map +1 -0
  76. package/dist/features/code-edit.js +412 -0
  77. package/dist/features/code-edit.js.map +1 -0
  78. package/dist/features/code-format.d.ts +21 -0
  79. package/dist/features/code-format.d.ts.map +1 -0
  80. package/dist/features/code-format.js +410 -0
  81. package/dist/features/code-format.js.map +1 -0
  82. package/dist/features/doc.d.ts +16 -0
  83. package/dist/features/doc.d.ts.map +1 -1
  84. package/dist/features/doc.js +154 -2
  85. package/dist/features/doc.js.map +1 -1
  86. package/dist/features/heading.d.ts.map +1 -1
  87. package/dist/features/heading.js.map +1 -1
  88. package/dist/features/image.d.ts +3 -1
  89. package/dist/features/image.d.ts.map +1 -1
  90. package/dist/features/image.js +27 -9
  91. package/dist/features/image.js.map +1 -1
  92. package/dist/features/index.d.ts +7 -0
  93. package/dist/features/index.d.ts.map +1 -1
  94. package/dist/features/index.js +16 -0
  95. package/dist/features/index.js.map +1 -1
  96. package/dist/features/inline-style.d.ts.map +1 -1
  97. package/dist/features/inline-style.js +4 -1
  98. package/dist/features/inline-style.js.map +1 -1
  99. package/dist/features/link.d.ts.map +1 -1
  100. package/dist/features/link.js +1 -1
  101. package/dist/features/link.js.map +1 -1
  102. package/dist/features/markdown.d.ts +15 -0
  103. package/dist/features/markdown.d.ts.map +1 -0
  104. package/dist/features/markdown.js +80 -0
  105. package/dist/features/markdown.js.map +1 -0
  106. package/dist/features/marks.d.ts.map +1 -1
  107. package/dist/features/marks.js +49 -4
  108. package/dist/features/marks.js.map +1 -1
  109. package/dist/features/mention.d.ts +30 -0
  110. package/dist/features/mention.d.ts.map +1 -0
  111. package/dist/features/mention.js +89 -0
  112. package/dist/features/mention.js.map +1 -0
  113. package/dist/features/script.d.ts.map +1 -1
  114. package/dist/features/script.js.map +1 -1
  115. package/dist/features/search.d.ts +76 -0
  116. package/dist/features/search.d.ts.map +1 -0
  117. package/dist/features/search.js +281 -0
  118. package/dist/features/search.js.map +1 -0
  119. package/dist/features/shared.d.ts +69 -0
  120. package/dist/features/shared.d.ts.map +1 -0
  121. package/dist/features/shared.js +272 -0
  122. package/dist/features/shared.js.map +1 -0
  123. package/dist/features/table.d.ts.map +1 -1
  124. package/dist/features/table.js +17 -3
  125. package/dist/features/table.js.map +1 -1
  126. package/dist/features/testkit.d.ts.map +1 -1
  127. package/dist/features/testkit.js +1 -1
  128. package/dist/features/testkit.js.map +1 -1
  129. package/dist/history.d.ts.map +1 -1
  130. package/dist/history.js +65 -20
  131. package/dist/history.js.map +1 -1
  132. package/dist/index.d.ts +21 -7
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +18 -6
  135. package/dist/index.js.map +1 -1
  136. package/dist/model/schema.d.ts.map +1 -1
  137. package/dist/model/schema.js +2 -1
  138. package/dist/model/schema.js.map +1 -1
  139. package/dist/parse/html.d.ts.map +1 -1
  140. package/dist/parse/html.js +38 -6
  141. package/dist/parse/html.js.map +1 -1
  142. package/dist/parse/sanitize.d.ts.map +1 -1
  143. package/dist/parse/sanitize.js +4 -1
  144. package/dist/parse/sanitize.js.map +1 -1
  145. package/dist/presets/highlight.d.ts.map +1 -1
  146. package/dist/presets/highlight.js +1 -4
  147. package/dist/presets/highlight.js.map +1 -1
  148. package/dist/presets/markdown-parse.d.ts +13 -0
  149. package/dist/presets/markdown-parse.d.ts.map +1 -0
  150. package/dist/presets/markdown-parse.js +398 -0
  151. package/dist/presets/markdown-parse.js.map +1 -0
  152. package/dist/presets/markdown.d.ts +46 -0
  153. package/dist/presets/markdown.d.ts.map +1 -0
  154. package/dist/presets/markdown.js +196 -0
  155. package/dist/presets/markdown.js.map +1 -0
  156. package/dist/state/apply.d.ts.map +1 -1
  157. package/dist/state/apply.js +25 -13
  158. package/dist/state/apply.js.map +1 -1
  159. package/dist/state/delete-ops.d.ts +34 -0
  160. package/dist/state/delete-ops.d.ts.map +1 -0
  161. package/dist/state/delete-ops.js +204 -0
  162. package/dist/state/delete-ops.js.map +1 -0
  163. package/dist/state/input-rules.d.ts +30 -0
  164. package/dist/state/input-rules.d.ts.map +1 -0
  165. package/dist/state/input-rules.js +70 -0
  166. package/dist/state/input-rules.js.map +1 -0
  167. package/dist/state/insert-ops.d.ts +54 -0
  168. package/dist/state/insert-ops.d.ts.map +1 -0
  169. package/dist/state/insert-ops.js +186 -0
  170. package/dist/state/insert-ops.js.map +1 -0
  171. package/dist/state/list-ops.d.ts +46 -0
  172. package/dist/state/list-ops.d.ts.map +1 -0
  173. package/dist/state/list-ops.js +231 -0
  174. package/dist/state/list-ops.js.map +1 -0
  175. package/dist/state/navigate.d.ts.map +1 -1
  176. package/dist/state/navigate.js +3 -1
  177. package/dist/state/navigate.js.map +1 -1
  178. package/dist/state/resolve-insert.d.ts +55 -0
  179. package/dist/state/resolve-insert.d.ts.map +1 -0
  180. package/dist/state/resolve-insert.js +118 -0
  181. package/dist/state/resolve-insert.js.map +1 -0
  182. package/dist/state/slice-ops.d.ts +46 -0
  183. package/dist/state/slice-ops.d.ts.map +1 -0
  184. package/dist/state/slice-ops.js +150 -0
  185. package/dist/state/slice-ops.js.map +1 -0
  186. package/dist/state/state.d.ts +6 -1
  187. package/dist/state/state.d.ts.map +1 -1
  188. package/dist/state/state.js +10 -0
  189. package/dist/state/state.js.map +1 -1
  190. package/dist/state/transaction.d.ts +43 -49
  191. package/dist/state/transaction.d.ts.map +1 -1
  192. package/dist/state/transaction.js +62 -858
  193. package/dist/state/transaction.js.map +1 -1
  194. package/dist/styles.css +401 -213
  195. package/dist/theme.d.ts +29 -0
  196. package/dist/theme.d.ts.map +1 -0
  197. package/dist/theme.js +347 -0
  198. package/dist/theme.js.map +1 -0
  199. package/dist/view/adapter.d.ts +84 -0
  200. package/dist/view/adapter.d.ts.map +1 -0
  201. package/dist/view/adapter.js +76 -0
  202. package/dist/view/adapter.js.map +1 -0
  203. package/dist/view/caret.d.ts +25 -0
  204. package/dist/view/caret.d.ts.map +1 -0
  205. package/dist/view/caret.js +43 -0
  206. package/dist/view/caret.js.map +1 -0
  207. package/dist/view/decoration.d.ts +9 -0
  208. package/dist/view/decoration.d.ts.map +1 -0
  209. package/dist/view/decoration.js +214 -0
  210. package/dist/view/decoration.js.map +1 -0
  211. package/dist/view/dirty-keys.d.ts +18 -0
  212. package/dist/view/dirty-keys.d.ts.map +1 -0
  213. package/dist/view/dirty-keys.js +31 -0
  214. package/dist/view/dirty-keys.js.map +1 -0
  215. package/dist/view/dom.d.ts.map +1 -1
  216. package/dist/view/dom.js +7 -5
  217. package/dist/view/dom.js.map +1 -1
  218. package/dist/view/hook-ctx.d.ts +32 -0
  219. package/dist/view/hook-ctx.d.ts.map +1 -0
  220. package/dist/view/hook-ctx.js +64 -0
  221. package/dist/view/hook-ctx.js.map +1 -0
  222. package/dist/view/ime.d.ts +72 -0
  223. package/dist/view/ime.d.ts.map +1 -0
  224. package/dist/view/ime.js +185 -0
  225. package/dist/view/ime.js.map +1 -0
  226. package/dist/view/input.d.ts +12 -12
  227. package/dist/view/input.d.ts.map +1 -1
  228. package/dist/view/input.js +156 -535
  229. package/dist/view/input.js.map +1 -1
  230. package/dist/view/keys.d.ts +25 -0
  231. package/dist/view/keys.d.ts.map +1 -0
  232. package/dist/view/keys.js +106 -0
  233. package/dist/view/keys.js.map +1 -0
  234. package/dist/view/line-nav.d.ts +13 -0
  235. package/dist/view/line-nav.d.ts.map +1 -0
  236. package/dist/view/line-nav.js +114 -0
  237. package/dist/view/line-nav.js.map +1 -0
  238. package/dist/view/mount.d.ts.map +1 -1
  239. package/dist/view/mount.js +43 -39
  240. package/dist/view/mount.js.map +1 -1
  241. package/dist/view/node-selection.d.ts +33 -0
  242. package/dist/view/node-selection.d.ts.map +1 -0
  243. package/dist/view/node-selection.js +107 -0
  244. package/dist/view/node-selection.js.map +1 -0
  245. package/dist/view/render.d.ts +4 -1
  246. package/dist/view/render.d.ts.map +1 -1
  247. package/dist/view/render.js +22 -3
  248. package/dist/view/render.js.map +1 -1
  249. package/dist/view/serialize.d.ts.map +1 -1
  250. package/dist/view/serialize.js +14 -5
  251. package/dist/view/serialize.js.map +1 -1
  252. package/package.json +1 -1
@@ -0,0 +1,398 @@
1
+ import { createDoc } from "../model/node.js";
2
+ /**
3
+ * Markdown → doc 노드 파서 — serializeToMarkdown의 역방향.
4
+ *
5
+ * 완전한 CommonMark가 아니라 "이 직렬화기가 출력하는 것 + 흔한
6
+ * 붙여넣기 마크다운"을 커버하는 경량 라인 파서다:
7
+ * 블록 — ATX 제목, ``` 펜스 코드, > 인용, 불릿(-, *, +)·숫자
8
+ * 리스트(중첩), - [ ] task, ---/___/*** 구분선, | 표 |, 문단
9
+ * 인라인 — 별표/언더바 굵기·기울임, ~~, `code`, [t](u), ![a](s),
10
+ * 백슬래시 이스케이프, 줄 끝 공백 2개 = hardBreak,
11
+ * 인라인 HTML(<u>/<mark>/<sup>/<sub>/<span style|data-mark>)
12
+ * — serializer가 HTML로 보존한 마크의 왕복 경로다
13
+ *
14
+ * 알 수 없는 구조는 텍스트로 남겨 내용을 잃지 않는다. 외부 도구
15
+ * (붙여넣기, 가져오기)가 에디터 인스턴스 없이 호출할 수 있도록
16
+ * 순수 함수다 — 스키마를 받지 않고 스타터 타입명을 그대로 만든다.
17
+ */
18
+ // ── 블록 토큰 ─────────────────────────────────────────────
19
+ const RE_BLANK = /^\s*$/;
20
+ const RE_FENCE = /^ {0,3}```([^`]*)\s*$/;
21
+ const RE_HEADING = /^ {0,3}(#{1,6})[ \t]+(.*)$/;
22
+ const RE_HR = /^ {0,3}(?:\*{3,}|-{3,}|_{3,})[ \t]*$/;
23
+ const RE_QUOTE = /^ {0,3}>[ \t]?(.*)$/;
24
+ const RE_LIST = /^(\s*)([-*+]|\d{1,9}[.)])[ \t]+(.*)$/;
25
+ const RE_TABLE_SEP = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(\|[ \t]*:?-+:?[ \t]*)+\|?[ \t]*$/;
26
+ /** 문단 수집을 끝내는 블록 시작 라인인가 (표는 다음 줄을 봐야 하므로 별도) */
27
+ function isBlockStart(line) {
28
+ return (RE_FENCE.test(line) ||
29
+ RE_HEADING.test(line) ||
30
+ RE_HR.test(line) ||
31
+ RE_QUOTE.test(line) ||
32
+ RE_LIST.test(line));
33
+ }
34
+ /**
35
+ * 줄 배열의 [0, len) 구간을 블록 노드들로 파싱한다.
36
+ * 인용구·리스트 아이템이 재귀로 같은 함수를 다시 부른다.
37
+ */
38
+ function parseBlocks(lines) {
39
+ const out = [];
40
+ let i = 0;
41
+ while (i < lines.length) {
42
+ const line = lines[i];
43
+ if (RE_BLANK.test(line)) {
44
+ i++;
45
+ continue;
46
+ }
47
+ // 펜스 코드 — 닫는 ```까지 원문 그대로
48
+ const fence = RE_FENCE.exec(line);
49
+ if (fence) {
50
+ const code = [];
51
+ i++;
52
+ while (i < lines.length && !RE_FENCE.test(lines[i])) {
53
+ code.push(lines[i]);
54
+ i++;
55
+ }
56
+ i++; // 닫는 펜스 (없으면 끝까지 소비)
57
+ const lang = fence[1].trim();
58
+ out.push({
59
+ type: "codeBlock",
60
+ ...(lang ? { attrs: { language: lang } } : {}),
61
+ content: [{ type: "text", text: code.join("\n") }],
62
+ });
63
+ continue;
64
+ }
65
+ const h = RE_HEADING.exec(line);
66
+ if (h) {
67
+ out.push({
68
+ type: "heading",
69
+ attrs: { level: h[1].length },
70
+ content: parseInlines(h[2].replace(/[ \t#]+$/, "")),
71
+ });
72
+ i++;
73
+ continue;
74
+ }
75
+ if (RE_HR.test(line)) {
76
+ out.push({ type: "horizontalRule" });
77
+ i++;
78
+ continue;
79
+ }
80
+ const q = RE_QUOTE.exec(line);
81
+ if (q) {
82
+ // 연속된 > 라인을 모아 접두사를 벗기고 재귀 파싱
83
+ const inner = [];
84
+ while (i < lines.length) {
85
+ const m = RE_QUOTE.exec(lines[i]);
86
+ if (!m)
87
+ break;
88
+ inner.push(m[1]);
89
+ i++;
90
+ }
91
+ out.push({ type: "blockquote", content: parseBlocks(inner) });
92
+ continue;
93
+ }
94
+ if (RE_LIST.test(line)) {
95
+ const r = parseList(lines, i);
96
+ out.push(r.node);
97
+ i = r.next;
98
+ continue;
99
+ }
100
+ // 표 — 현재 줄에 |가 있고 다음 줄이 구분 행이어야 한다
101
+ if (line.includes("|") && RE_TABLE_SEP.test(lines[i + 1] ?? "")) {
102
+ const r = parseTable(lines, i);
103
+ out.push(r.node);
104
+ i = r.next;
105
+ continue;
106
+ }
107
+ // 문단 — 빈 줄이나 다른 블록 시작까지
108
+ const buf = [];
109
+ while (i < lines.length &&
110
+ !RE_BLANK.test(lines[i]) &&
111
+ !(buf.length > 0 && isBlockStart(lines[i])) &&
112
+ !(buf.length > 0 &&
113
+ lines[i].includes("|") &&
114
+ RE_TABLE_SEP.test(lines[i + 1] ?? ""))) {
115
+ buf.push(lines[i]);
116
+ i++;
117
+ }
118
+ // 줄 끝 공백 2개 = hardBreak (serializer 계약) — 이스케이프가
119
+ // 인라인 파싱 전에 적용되도록 \u0000 센티널로 번역한다
120
+ const src = buf.join("\n").replace(/ {2,}\n/g, "\u0000");
121
+ out.push({ type: "paragraph", content: parseInlines(src) });
122
+ }
123
+ return out;
124
+ }
125
+ /** 리스트 한 덩어리 — 같은 들여쓰기의 연속 아이템 + 각 아이템의 중첩 내용 */
126
+ function parseList(lines, start) {
127
+ const first = RE_LIST.exec(lines[start]);
128
+ const base = first[1].length;
129
+ const ordered = /\d/.test(first[2]);
130
+ const startNum = ordered ? parseInt(first[2], 10) : 1;
131
+ // 첫 아이템이 체크박스면 taskList — GFM 관례상 리스트 전체가 taskItem
132
+ const task = /^\[[ xX]\]\s+/.test(first[3]);
133
+ const items = [];
134
+ let i = start;
135
+ while (i < lines.length) {
136
+ const l = lines[i];
137
+ if (RE_BLANK.test(l))
138
+ break;
139
+ const m = RE_LIST.exec(l);
140
+ if (m && m[1].length === base) {
141
+ items.push([m[3]]);
142
+ i++;
143
+ continue;
144
+ }
145
+ // 중첩/연속 라인 — 마커보다 깊이 들여쓴 줄은 현재 아이템의 자식.
146
+ // 마커 폭(기호+공백)만큼 벗겨내 아이템 좌표계로 내린다
147
+ const indent = /^\s*/.exec(l)[0].length;
148
+ if (items.length && indent > base) {
149
+ items[items.length - 1].push(l.slice(Math.min(indent, base + 2)));
150
+ i++;
151
+ continue;
152
+ }
153
+ break;
154
+ }
155
+ const itemType = task ? "taskItem" : "listItem";
156
+ const content = items.map((itemLines) => {
157
+ let checked;
158
+ let first = itemLines[0];
159
+ if (task) {
160
+ const c = /^\[([ xX])\]\s+(.*)$/.exec(first);
161
+ checked = c ? c[1].toLowerCase() === "x" : false;
162
+ first = c ? c[2] : first;
163
+ }
164
+ const rest = itemLines.slice(1);
165
+ const children = rest.length
166
+ ? [
167
+ { type: "paragraph", content: parseInlines(first) },
168
+ ...parseBlocks(rest),
169
+ ]
170
+ : [{ type: "paragraph", content: parseInlines(first) }];
171
+ return {
172
+ type: itemType,
173
+ ...(checked !== undefined ? { attrs: { checked } } : {}),
174
+ content: children,
175
+ };
176
+ });
177
+ const node = ordered
178
+ ? {
179
+ type: "orderedList",
180
+ ...(startNum !== 1 ? { attrs: { start: startNum } } : {}),
181
+ content,
182
+ }
183
+ : { type: task ? "taskList" : "bulletList", content };
184
+ return { node, next: i };
185
+ }
186
+ /** 표 — 첫 행 + 구분 행 + 데이터 행들. 셀의 \| 이스케이프를 복원한다 */
187
+ function parseTable(lines, start) {
188
+ const splitRow = (line) => line
189
+ .trim()
190
+ .replace(/^\||\|$/g, "")
191
+ .split(/(?<!\\)\|/)
192
+ .map((c) => c.trim().replace(/\\\|/g, "|"));
193
+ const rows = [splitRow(lines[start])];
194
+ let i = start + 2; // 헤더 행 + 구분 행 소비
195
+ while (i < lines.length &&
196
+ lines[i].includes("|") &&
197
+ !RE_BLANK.test(lines[i])) {
198
+ rows.push(splitRow(lines[i]));
199
+ i++;
200
+ }
201
+ return {
202
+ node: {
203
+ type: "table",
204
+ content: rows.map((cells) => ({
205
+ type: "tableRow",
206
+ content: cells.map((c) => ({
207
+ type: "tableCell",
208
+ content: [{ type: "paragraph", content: parseInlines(c) }],
209
+ })),
210
+ })),
211
+ },
212
+ next: i,
213
+ };
214
+ }
215
+ // ── 인라인 ────────────────────────────────────────────────
216
+ /** 이스케이프 가능한 마크다운 구두점 — serializer의 escMd와 같은 집합 */
217
+ const RE_ESCAPABLE = /[\\`*_[\]{}()<>#+\-.!|~]/;
218
+ /** 같은 마크 배열이면 직전 텍스트 노드에 합친다 — 파서 산출물 정리용 */
219
+ function pushText(out, text, marks) {
220
+ if (!text)
221
+ return;
222
+ const last = out[out.length - 1];
223
+ if (last?.type === "text" &&
224
+ JSON.stringify(last.marks ?? null) ===
225
+ JSON.stringify(marks.length ? marks : null)) {
226
+ out[out.length - 1] = { ...last, text: (last.text ?? "") + text };
227
+ return;
228
+ }
229
+ out.push({
230
+ type: "text",
231
+ text,
232
+ ...(marks.length ? { marks: [...marks] } : {}),
233
+ });
234
+ }
235
+ /**
236
+ * 인라인 HTML 마크 — serializer가 HTML 미지원 마크를 태그로 보존한
237
+ * 것을 되돌린다. 매칭 안 되는 태그는 null (본문 텍스트로 둔다).
238
+ */
239
+ function htmlMark(tag, attrSrc) {
240
+ const style = /style="([^"]*)"/.exec(attrSrc)?.[1] ?? "";
241
+ switch (tag) {
242
+ case "u":
243
+ return { type: "underline" };
244
+ case "sup":
245
+ return { type: "superscript" };
246
+ case "sub":
247
+ return { type: "subscript" };
248
+ case "mark": {
249
+ const c = /background-color:\s*([^;]+)/.exec(style)?.[1]?.trim();
250
+ return { type: "highlight", ...(c ? { attrs: { color: c } } : {}) };
251
+ }
252
+ case "span": {
253
+ const dm = /data-mark="([^"]+)"/.exec(attrSrc)?.[1];
254
+ if (dm)
255
+ return { type: dm };
256
+ const fs = /font-size:\s*([^;]+)/.exec(style)?.[1]?.trim();
257
+ if (fs)
258
+ return { type: "fontSize", attrs: { size: fs } };
259
+ const col = /(?:^|;)\s*color:\s*([^;]+)/.exec(style)?.[1]?.trim();
260
+ if (col)
261
+ return { type: "textColor", attrs: { color: col } };
262
+ return null;
263
+ }
264
+ default:
265
+ return null;
266
+ }
267
+ }
268
+ /**
269
+ * 인라인 파서 — src를 텍스트/마크/인라인 노드 배열로 번역한다.
270
+ * extra는 바깥 마크(링크·굵기 등) — 닫는 구분자 안쪽을 재귀할 때 누적.
271
+ * 구분자 매칭은 단순 앞뒤 탐색이라 `***x***` 같은 혼합 중첩은
272
+ * 정확하지 않을 수 있다 — 경량 파서의 의도된 한계.
273
+ */
274
+ function parseInlines(src, extra = []) {
275
+ const out = [];
276
+ let plain = "";
277
+ const flush = () => {
278
+ if (plain) {
279
+ pushText(out, plain, extra);
280
+ plain = "";
281
+ }
282
+ };
283
+ const pushAll = (nodes) => {
284
+ for (const n of nodes)
285
+ out.push(n);
286
+ };
287
+ let i = 0;
288
+ while (i < src.length) {
289
+ const ch = src[i];
290
+ // 백슬래시 이스케이프 — 구두점만 문자로, 나머지는 \ 그대로
291
+ if (ch === "\\" && RE_ESCAPABLE.test(src[i + 1] ?? "")) {
292
+ plain += src[i + 1];
293
+ i += 2;
294
+ continue;
295
+ }
296
+ // hardBreak 센티널 — 블록 파서가 줄 끝 공백 2개를 번역한 것
297
+ if (ch === "\u0000") {
298
+ flush();
299
+ out.push({ type: "hardBreak" });
300
+ i++;
301
+ continue;
302
+ }
303
+ // 문단 안의 부드러운 줄바꿈 — 공백으로 접합
304
+ if (ch === "\n") {
305
+ plain += " ";
306
+ i++;
307
+ continue;
308
+ }
309
+ if (ch === "`") {
310
+ const end = src.indexOf("`", i + 1);
311
+ if (end > i + 1) {
312
+ flush();
313
+ // 인라인 코드는 내부를 파싱하지 않는다 — 원문 보존
314
+ pushText(out, src.slice(i + 1, end), [...extra, { type: "code" }]);
315
+ i = end + 1;
316
+ continue;
317
+ }
318
+ }
319
+ if (ch === "!" && src[i + 1] === "[") {
320
+ const m = /^!\[([^\]]*)\]\(([^)\s]*)\)/.exec(src.slice(i));
321
+ if (m) {
322
+ flush();
323
+ out.push({
324
+ type: "image",
325
+ attrs: { src: m[2] ?? "", alt: m[1] ?? "" },
326
+ });
327
+ i += m[0].length;
328
+ continue;
329
+ }
330
+ }
331
+ if (ch === "[") {
332
+ const m = /^\[([^\]]*)\]\(([^)\s]*)\)/.exec(src.slice(i));
333
+ if (m) {
334
+ flush();
335
+ pushAll(parseInlines(m[1] ?? "", [
336
+ ...extra,
337
+ { type: "link", attrs: { href: m[2] ?? "" } },
338
+ ]));
339
+ i += m[0].length;
340
+ continue;
341
+ }
342
+ }
343
+ if (ch === "*" || ch === "_") {
344
+ const two = src[i + 1] === ch;
345
+ const d = two ? ch + ch : ch;
346
+ const end = src.indexOf(d, i + d.length);
347
+ if (end >= i + d.length) {
348
+ flush();
349
+ pushAll(parseInlines(src.slice(i + d.length, end), [
350
+ ...extra,
351
+ { type: two ? "bold" : "italic" },
352
+ ]));
353
+ i = end + d.length;
354
+ continue;
355
+ }
356
+ }
357
+ if (ch === "~" && src[i + 1] === "~") {
358
+ const end = src.indexOf("~~", i + 2);
359
+ if (end >= i + 2) {
360
+ flush();
361
+ pushAll(parseInlines(src.slice(i + 2, end), [...extra, { type: "strike" }]));
362
+ i = end + 2;
363
+ continue;
364
+ }
365
+ }
366
+ if (ch === "<") {
367
+ const m = /^<(u|sup|sub|mark|span)(\s[^>]*)?>([\s\S]*?)<\/\1>/.exec(src.slice(i));
368
+ if (m) {
369
+ flush();
370
+ const mark = htmlMark(m[1], m[2] ?? "");
371
+ pushAll(parseInlines(m[3] ?? "", mark ? [...extra, mark] : extra));
372
+ i += m[0].length;
373
+ continue;
374
+ }
375
+ }
376
+ plain += ch;
377
+ i++;
378
+ }
379
+ flush();
380
+ return out;
381
+ }
382
+ /**
383
+ * 마크다운 문자열 → doc 자식 블록 배열.
384
+ * 반환값은 insertSlice의 nodes 인자 형태다 — doc 래퍼는 호출부가
385
+ * `createDoc(parseMarkdown(md))`로 감싼다. 빈 입력은 빈 배열.
386
+ */
387
+ export function parseMarkdown(md) {
388
+ return parseBlocks(md.split("\n"));
389
+ }
390
+ /**
391
+ * 마크다운 문자열 → DocNode — doc 초기화/전체 교체용.
392
+ * 빈 입력도 빈 문단 하나를 만들어 "doc은 최소 한 블록" 계약을 지킨다.
393
+ */
394
+ export function docFromMarkdown(md) {
395
+ const content = parseMarkdown(md);
396
+ return createDoc(content.length ? content : [{ type: "paragraph" }]);
397
+ }
398
+ //# sourceMappingURL=markdown-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-parse.js","sourceRoot":"","sources":["../../src/presets/markdown-parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { EditorNode, Mark } from "../model/node.js";
2
+ /**
3
+ * doc → Markdown 직렬화.
4
+ *
5
+ * serializeToHTML이 스키마 toDOM을 따라가는 것과 달리, 마크다운은
6
+ * "스키마와 무관하게 AST 타입으로 번역"하는 별도 채널이다 — 스키마
7
+ * 레지스트리 없이 호출할 수 있어 외부 도구(내보내기, 클립보드)가
8
+ * 에디터 인스턴스 없이도 쓸 수 있다.
9
+ *
10
+ * 기본 테이블은 스타터 스키마 타입을 커버한다. 커스텀 노드/마크는
11
+ * options.nodes/marks 오버라이드로 얹는다 — 오버라이드가 없는
12
+ * 알 수 없는 타입은 자식 블록을 유지해 직렬화하고(인라인 자식이면
13
+ * 인라인 폴백) 내용을 잃지 않는다.
14
+ *
15
+ * HTML로 표현되지 않는 마크(밑줄·형광펜·색상·크기·첨자)는 인라인
16
+ * HTML 태그로 보존한다 — CommonMark가 인라인 HTML을 허용하므로
17
+ * 왕복 시 정보가 살아남는다.
18
+ */
19
+ /** 마크 직렬화 — 안쪽 텍스트를 감싸는 래퍼 */
20
+ export type MdMarkSerializer = (inner: string, mark: Mark) => string;
21
+ /**
22
+ * 노드 직렬화 — 블록 노드를 마크다운 문자열로 번역한다.
23
+ * ctx.join(children)으로 자식을 먼저 직렬화한 뒤 꾸밀 수 있다.
24
+ */
25
+ export type MdNodeSerializer = (node: EditorNode, ctx: MdSerializeContext) => string;
26
+ /** 노드 핸들러에 넘기는 재귀 컨텍스트 */
27
+ export interface MdSerializeContext {
28
+ /** 자식 노드들의 인라인 직렬화 합본 */
29
+ inline(node: EditorNode): string;
30
+ /** 자식 블록들을 빈 줄로 잇는다 — 리스트 아이템 안의 중첩 블록용 */
31
+ blocks(node: EditorNode, depth: number): string;
32
+ /** 현재 리스트 중첩 깊이 — 들여쓰기 계산용 */
33
+ readonly depth: number;
34
+ }
35
+ export interface MarkdownSerializeOptions {
36
+ /** 노드 타입별 블록 핸들러 오버라이드/추가 */
37
+ nodes?: Record<string, MdNodeSerializer>;
38
+ /** 마크 타입별 래퍼 오버라이드/추가 */
39
+ marks?: Record<string, MdMarkSerializer>;
40
+ }
41
+ /**
42
+ * doc → 마크다운 문자열.
43
+ * 블록 사이는 빈 줄(\n\n), 문서 끝은 개행 하나로 닫는다.
44
+ */
45
+ export declare function serializeToMarkdown(doc: EditorNode, options?: MarkdownSerializeOptions): string;
46
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/presets/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;;;;;;;;;;;;GAgBG;AAEH,8BAA8B;AAC9B,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,kBAAkB,KACpB,MAAM,CAAC;AAEZ,0BAA0B;AAC1B,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAChD,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACzC,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC1C;AAiND;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAgBR"}
@@ -0,0 +1,196 @@
1
+ // ── 인라인 ─────────────────────────────────────────────────
2
+ /**
3
+ * 마크다운 특수문자 이스케이프 — CommonMark는 모든 ASCII 구두점에
4
+ * 백슬래시 이스케이프를 허용한다. 감싸는 마크 문법(*, ~~ 등)은
5
+ * 이스케이프 이후에 씌워지므로 오염되지 않는다.
6
+ */
7
+ function escMd(s) {
8
+ return s.replace(/([\\`*_[\]{}()<>#+\-.!|~])/g, "\\$1");
9
+ }
10
+ /** attrs 값 → HTML 속성 이스케이프 (인라인 HTML 태그용) */
11
+ function escHtmlAttr(v) {
12
+ return String(v ?? "")
13
+ .replace(/&/g, "&amp;")
14
+ .replace(/"/g, "&quot;");
15
+ }
16
+ /**
17
+ * 마크 적용 순서 — 안쪽→바깥쪽 고정 우선순위.
18
+ * code가 최내곽(내용 이스케이프 없이 원문 보존), link가 최외곽.
19
+ * AST의 marks 배열 순서와 무관하게 출력이 결정적이게 한다.
20
+ */
21
+ const MARK_ORDER = [
22
+ "code",
23
+ "strike",
24
+ "italic",
25
+ "bold",
26
+ "underline",
27
+ "subscript",
28
+ "superscript",
29
+ "highlight",
30
+ "fontSize",
31
+ "textColor",
32
+ "link",
33
+ ];
34
+ const DEFAULT_MARKS = {
35
+ // code는 내용을 이스케이프하지 않는다 — 호출부가 원문을 넘긴다
36
+ code: (s) => `\`${s}\``,
37
+ strike: (s) => `~~${s}~~`,
38
+ italic: (s) => `*${s}*`,
39
+ bold: (s) => `**${s}**`,
40
+ underline: (s) => `<u>${s}</u>`,
41
+ subscript: (s) => `<sub>${s}</sub>`,
42
+ superscript: (s) => `<sup>${s}</sup>`,
43
+ highlight: (s, m) => m.attrs?.["color"]
44
+ ? `<mark style="background-color: ${escHtmlAttr(m.attrs["color"])}">${s}</mark>`
45
+ : `<mark>${s}</mark>`,
46
+ fontSize: (s, m) => `<span style="font-size: ${escHtmlAttr(m.attrs?.["size"])}">${s}</span>`,
47
+ textColor: (s, m) => `<span style="color: ${escHtmlAttr(m.attrs?.["color"])}">${s}</span>`,
48
+ link: (s, m) => `[${s}](${String(m.attrs?.["href"] ?? "")})`,
49
+ };
50
+ /** 텍스트 노드 → 마크가 적용된 마크다운 인라인 */
51
+ function mdText(node, marks) {
52
+ const raw = node.text ?? "";
53
+ const list = node.marks ?? [];
54
+ const code = list.find((m) => m.type === "code");
55
+ // 인라인 코드는 내용을 이스케이프하지 않는다 — 백슬래시가 글자로 남는다
56
+ let s = code ? raw : escMd(raw);
57
+ for (const t of MARK_ORDER) {
58
+ const m = list.find((x) => x.type === t);
59
+ if (!m)
60
+ continue;
61
+ const wrap = marks[t] ?? ((x) => x);
62
+ s = wrap(s, m);
63
+ }
64
+ // 스키마에 없는 마크도 잃지 않는다 — HTML span으로 보존
65
+ for (const m of list) {
66
+ if (MARK_ORDER.includes(m.type) || marks[m.type])
67
+ continue;
68
+ s = `<span data-mark="${escHtmlAttr(m.type)}">${s}</span>`;
69
+ }
70
+ return s;
71
+ }
72
+ /** 인라인 자식 합본 — 비텍스트 인라인 노드(hardBreak/mention/image)도 번역 */
73
+ function mdInline(node, ctx) {
74
+ return (node.content ?? [])
75
+ .map((c) => c.type === "text" ? mdText(c, ctx.marks) : mdInlineLeaf(c, ctx))
76
+ .join("");
77
+ }
78
+ /** 인라인 리프 노드 — 블록 컨텍스트에서도 나올 수 있다 (image는 블록 atom) */
79
+ function mdInlineLeaf(node, ctx) {
80
+ switch (node.type) {
81
+ case "hardBreak":
82
+ return " \n"; // 마크다운 하드 브레이크 — 뒤공백 2개 + 개행
83
+ case "image":
84
+ return `![${escMd(String(node.attrs?.["alt"] ?? ""))}](${String(node.attrs?.["src"] ?? "")})`;
85
+ case "mention":
86
+ return `@${escMd(String(node.attrs?.["label"] ?? node.attrs?.["id"] ?? ""))}`;
87
+ default:
88
+ // 커스텀 인라인(콜아웃 내부 등)은 자식 인라인으로 폴백
89
+ return mdInline(node, ctx);
90
+ }
91
+ }
92
+ /** 리스트 아이템의 내용 — 첫 블록은 마커 뒤에, 이후 블록은 들여쓴다 */
93
+ function mdListItem(item, marker, ctx) {
94
+ const pad = " ".repeat(marker.length);
95
+ const kids = item.content ?? [];
96
+ if (!kids.length)
97
+ return marker.trimEnd();
98
+ const parts = kids.map((k) => mdBlock(k, ctx));
99
+ return parts
100
+ .map((p, i) => {
101
+ const indent = i === 0 ? marker : pad;
102
+ return p
103
+ .split("\n")
104
+ .map((line) => (line ? indent + line : indent.trimEnd()))
105
+ .join("\n");
106
+ })
107
+ .join("\n");
108
+ }
109
+ /** 기본 노드 핸들러 — 스타터 스키마 타입 */
110
+ const DEFAULT_NODES = {
111
+ paragraph: (n, ctx) => ctx.inline(n),
112
+ heading: (n, ctx) => {
113
+ const lv = Math.min(6, Math.max(1, Number(n.attrs?.["level"] ?? 1)));
114
+ return "#".repeat(lv) + " " + ctx.inline(n);
115
+ },
116
+ codeBlock: (n) => {
117
+ // 코드는 이스케이프 없이 원문 — 언어 접미사는 fence에 붙인다
118
+ const lang = String(n.attrs?.["language"] ?? "");
119
+ const code = (n.content ?? []).map((c) => c.text ?? "").join("");
120
+ return "```" + lang + "\n" + code + "\n```";
121
+ },
122
+ blockquote: (n, ctx) => ctx
123
+ .blocks(n, ctx.depth)
124
+ .split("\n")
125
+ .map((l) => (l ? "> " + l : ">"))
126
+ .join("\n"),
127
+ // 핸들러 ctx의 공개 타입은 MdSerializeContext지만 실제 런타임
128
+ // 객체는 항상 내부 Ctx — 리스트 중첩 시 depth만 바꿔 재귀한다
129
+ bulletList: (n, ctx) => (n.content ?? [])
130
+ .map((it) => mdListItem(it, "- ", { ...ctx, depth: ctx.depth + 1 }))
131
+ .join("\n"),
132
+ orderedList: (n, ctx) => {
133
+ const start = Number(n.attrs?.["start"] ?? 1);
134
+ return (n.content ?? [])
135
+ .map((it, i) => mdListItem(it, `${start + i}. `, {
136
+ ...ctx,
137
+ depth: ctx.depth + 1,
138
+ }))
139
+ .join("\n");
140
+ },
141
+ taskList: (n, ctx) => (n.content ?? [])
142
+ .map((it) => {
143
+ const mark = it.attrs?.["checked"] ? "- [x] " : "- [ ] ";
144
+ return mdListItem(it, mark, { ...ctx, depth: ctx.depth + 1 });
145
+ })
146
+ .join("\n"),
147
+ horizontalRule: () => "---",
148
+ image: (n) => `![${escMd(String(n.attrs?.["alt"] ?? ""))}](${String(n.attrs?.["src"] ?? "")})`,
149
+ table: (n, ctx) => {
150
+ // 마크다운 표는 한 줄 셀만 지원 — 셀 안 블록은 공백으로 잇고
151
+ // 파이프는 이스케이프한다. 첫 행을 헤더로 간주해 구분 행을 단다.
152
+ const rows = (n.content ?? []).map((r) => (r.content ?? []).map((cell) => (cell.content ?? [])
153
+ .map((b) => ctx.inline(b))
154
+ .join(" ")
155
+ .replace(/\|/g, "\\|")));
156
+ if (!rows.length)
157
+ return "";
158
+ const cols = Math.max(...rows.map((r) => r.length));
159
+ const header = "| " + Array(cols).fill("---").join(" | ") + " |";
160
+ const lines = rows.map((r) => `| ${r.join(" | ")} |`);
161
+ return [lines[0], header, ...lines.slice(1)].join("\n");
162
+ },
163
+ };
164
+ /** 블록 노드 하나 직렬화 — 오버라이드 → 기본 핸들러 → 인라인 폴백 */
165
+ function mdBlock(node, ctx) {
166
+ const custom = ctx.nodes[node.type];
167
+ if (custom)
168
+ return custom(node, ctx);
169
+ const def = DEFAULT_NODES[node.type];
170
+ if (def)
171
+ return def(node, ctx);
172
+ // 알 수 없는 블록 — 자식이 블록이면 블록 구조를 유지해 직렬화하고,
173
+ // 인라인/텍스트 자식이면 인라인 폴백한다. 어느 쪽이든 내용을 잃지 않는다
174
+ return ctx.blocks(node, ctx.depth) || ctx.inline(node);
175
+ }
176
+ /**
177
+ * doc → 마크다운 문자열.
178
+ * 블록 사이는 빈 줄(\n\n), 문서 끝은 개행 하나로 닫는다.
179
+ */
180
+ export function serializeToMarkdown(doc, options = {}) {
181
+ const marks = { ...DEFAULT_MARKS, ...(options.marks ?? {}) };
182
+ const nodes = options.nodes ?? {};
183
+ const ctx = {
184
+ depth: 0,
185
+ marks,
186
+ nodes,
187
+ inline: (n) => mdInline(n, ctx),
188
+ blocks: (n, depth) => (n.content ?? [])
189
+ .map((c) => mdBlock(c, { ...ctx, depth }))
190
+ .filter((s) => s !== "")
191
+ .join("\n\n"),
192
+ };
193
+ const body = ctx.blocks(doc, 0);
194
+ return body ? body + "\n" : "";
195
+ }
196
+ //# sourceMappingURL=markdown.js.map