@aeriz/wysiwyg 1.0.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 (36) hide show
  1. package/LICENSE +201 -0
  2. package/dist/aeriz-wysiwyg.css +1471 -0
  3. package/dist/aeriz-wysiwyg.esm.js +5602 -0
  4. package/dist/aeriz-wysiwyg.esm.min.js +4227 -0
  5. package/dist/aeriz-wysiwyg.js +5605 -0
  6. package/dist/aeriz-wysiwyg.min.css +1 -0
  7. package/dist/aeriz-wysiwyg.min.js +23 -0
  8. package/dist-types/core/AerizEditor.d.ts +337 -0
  9. package/dist-types/core/AerizEditor.d.ts.map +1 -0
  10. package/dist-types/core/AerizViewer.d.ts +49 -0
  11. package/dist-types/core/AerizViewer.d.ts.map +1 -0
  12. package/dist-types/core/blocks.d.ts +303 -0
  13. package/dist-types/core/blocks.d.ts.map +1 -0
  14. package/dist-types/core/fonts.d.ts +49 -0
  15. package/dist-types/core/fonts.d.ts.map +1 -0
  16. package/dist-types/core/highlight.d.ts +44 -0
  17. package/dist-types/core/highlight.d.ts.map +1 -0
  18. package/dist-types/core/i18n.d.ts +209 -0
  19. package/dist-types/core/i18n.d.ts.map +1 -0
  20. package/dist-types/core/import-html.d.ts +28 -0
  21. package/dist-types/core/import-html.d.ts.map +1 -0
  22. package/dist-types/core/import-markdown.d.ts +37 -0
  23. package/dist-types/core/import-markdown.d.ts.map +1 -0
  24. package/dist-types/index.d.ts +11 -0
  25. package/dist-types/index.d.ts.map +1 -0
  26. package/dist-types/types/index.d.ts +40 -0
  27. package/dist-types/types/index.d.ts.map +1 -0
  28. package/dist-types/ui/Toolbar.d.ts +21 -0
  29. package/dist-types/ui/Toolbar.d.ts.map +1 -0
  30. package/dist-types/ui/dialogs.d.ts +74 -0
  31. package/dist-types/ui/dialogs.d.ts.map +1 -0
  32. package/dist-types/ui/icons.d.ts +2 -0
  33. package/dist-types/ui/icons.d.ts.map +1 -0
  34. package/dist-types/ui/popovers.d.ts +70 -0
  35. package/dist-types/ui/popovers.d.ts.map +1 -0
  36. package/package.json +57 -0
@@ -0,0 +1,4227 @@
1
+ var Ue = Object.defineProperty;
2
+ var We = (n, e, t) => e in n ? Ue(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
+ var w = (n, e, t) => We(n, typeof e != "symbol" ? e + "" : e, t);
4
+ const Ke = {
5
+ items: [
6
+ // undo / redo 는 브라우저 네이티브 (Ctrl+Z / Ctrl+Y) 가 처리하므로 기본
7
+ // 툴바에서 제외. ToolbarItemName 멤버는 유지 — 커스텀 toolbar 에서
8
+ // 여전히 등록 가능.
9
+ "blockType",
10
+ "|",
11
+ // ── 텍스트 관련 그룹 ────────────────────────────
12
+ "fontFamily",
13
+ "fontSize",
14
+ "|",
15
+ "bold",
16
+ "italic",
17
+ "underline",
18
+ "strike",
19
+ "|",
20
+ "color",
21
+ "backgroundColor",
22
+ "|",
23
+ "align",
24
+ "|",
25
+ // ── 표 전용 그룹 ─────────────────────────────────────
26
+ // 활성 블록이 table 일 때만 보이며, 그 외 블록에서는 그룹 자체가 숨김
27
+ // 처리(syncGroupVisibility) 되어 toolbar 가 깔끔히 정돈된다.
28
+ "cellBg",
29
+ "tableMenu",
30
+ "codeLang"
31
+ // 모든 블록 유형(bulletList / orderedList / blockquote / codeBlock / hr /
32
+ // linkCard / image / table / youtube) 은 blockType 드롭다운이 일관 처리.
33
+ // clearFormat / image / table 등 단독 toolbar 항목은 기본에서 제거.
34
+ // 커스텀 toolbar 에서는 여전히 등록 가능.
35
+ ],
36
+ sticky: !0
37
+ }, E = {
38
+ undo: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-15-6.7L3 13"/></svg>',
39
+ redo: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 15-6.7L21 13"/></svg>',
40
+ blockType: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="5" rx="1.2"/><line x1="3" y1="13" x2="21" y2="13"/><line x1="3" y1="17" x2="21" y2="17"/><line x1="3" y1="21" x2="14" y2="21"/></svg>',
41
+ fontFamily: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18 L7 6 L11 18"/><line x1="4.5" y1="14" x2="9.5" y2="14"/><path d="M14 12 a3 3 0 1 1 0 6 a3 3 0 0 1 0 -6"/><line x1="20" y1="12" x2="20" y2="18"/></svg>',
42
+ fontSize: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6 L13 6"/><line x1="8" y1="6" x2="8" y2="19"/><polyline points="17 9 20 6 17 6"/><line x1="20" y1="6" x2="20" y2="18"/><polyline points="17 15 20 18 17 18"/></svg>',
43
+ bold: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 0 8H6z"/><path d="M6 12h9a4 4 0 0 1 0 8H6z"/></svg>',
44
+ italic: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>',
45
+ underline: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v7a6 6 0 0 0 12 0V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg>',
46
+ strike: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" y1="12" x2="20" y2="12"/></svg>',
47
+ color: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16"/><path d="M6 17l5-12 2-1 2 1 5 12"/><path d="M8 13h8"/></svg>',
48
+ backgroundColor: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12l9-9 9 9-9 9z"/><path d="M3 12h18"/></svg>',
49
+ alignLeft: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg>',
50
+ alignCenter: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="10" x2="6" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="18" y1="18" x2="6" y2="18"/></svg>',
51
+ alignRight: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="10" x2="7" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="7" y2="18"/></svg>',
52
+ alignJustify: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="3" y2="18"/></svg>',
53
+ bulletList: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1.5" fill="currentColor"/><circle cx="4.5" cy="12" r="1.5" fill="currentColor"/><circle cx="4.5" cy="18" r="1.5" fill="currentColor"/></svg>',
54
+ orderedList: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg>',
55
+ blockquote: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg>',
56
+ codeBlock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
57
+ link: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>',
58
+ image: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
59
+ table: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>',
60
+ hr: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="12" x2="20" y2="12"/></svg>',
61
+ clearFormat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M5 20h6"/><path d="M13 4 8 20"/><line x1="15" y1="15" x2="20" y2="20"/><line x1="20" y1="15" x2="15" y2="20"/></svg>',
62
+ chevronDown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
63
+ close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
64
+ upload: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>',
65
+ externalLink: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>',
66
+ youtube: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="3"/><polygon points="10 9.5 15 12 10 14.5" fill="currentColor" stroke="none"/></svg>',
67
+ importHtml: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h11l5 5v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z"/><path d="M14 4v5h5"/><path d="M12 12v6"/><polyline points="9 15 12 18 15 15"/></svg>',
68
+ importMarkdown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 15V9l3 3 3-3v6"/><path d="M16 9v6"/><polyline points="14 13 16 15 18 13"/></svg>',
69
+ attachFile: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57a4 4 0 0 1 5.66 5.66l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>',
70
+ cellBg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1.5"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="12" y1="3" x2="12" y2="21"/><rect x="3" y="3" width="9" height="9" fill="currentColor" opacity="0.35" stroke="none"/></svg>',
71
+ tableMenu: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1.5"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>',
72
+ codeLang: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/><line x1="14" y1="4" x2="10" y2="20"/></svg>'
73
+ }, Ye = {
74
+ undo: { name: "undo", label: "되돌리기", icon: "undo", shortcut: "Ctrl+Z" },
75
+ redo: { name: "redo", label: "다시 실행", icon: "redo", shortcut: "Ctrl+Y" },
76
+ blockType: { name: "blockType", label: "블록 유형", icon: "blockType", hasDropdown: !0 },
77
+ fontFamily: { name: "fontFamily", label: "글꼴", icon: "fontFamily", hasDropdown: !0 },
78
+ fontSize: { name: "fontSize", label: "글자 크기", icon: "fontSize", hasDropdown: !0 },
79
+ bold: { name: "bold", label: "굵게", icon: "bold", shortcut: "Ctrl+B" },
80
+ italic: { name: "italic", label: "기울임", icon: "italic", shortcut: "Ctrl+I" },
81
+ underline: { name: "underline", label: "밑줄", icon: "underline", shortcut: "Ctrl+U" },
82
+ strike: { name: "strike", label: "취소선", icon: "strike", shortcut: "Ctrl+Shift+X" },
83
+ color: { name: "color", label: "글자색", icon: "color", hasDropdown: !0 },
84
+ backgroundColor: { name: "backgroundColor", label: "배경색", icon: "backgroundColor", hasDropdown: !0 },
85
+ align: { name: "align", label: "정렬", icon: "alignLeft", hasDropdown: !0 },
86
+ cellBg: { name: "cellBg", label: "셀 배경색", icon: "cellBg", hasDropdown: !0 },
87
+ tableMenu: { name: "tableMenu", label: "표 편집", icon: "tableMenu", hasDropdown: !0 },
88
+ codeLang: { name: "codeLang", label: "코드 언어", icon: "codeLang", hasDropdown: !0 },
89
+ bulletList: { name: "bulletList", label: "글머리 기호", icon: "bulletList", shortcut: "Ctrl+Shift+8" },
90
+ orderedList: { name: "orderedList", label: "번호 매기기", icon: "orderedList", shortcut: "Ctrl+Shift+7" },
91
+ blockquote: { name: "blockquote", label: "인용구", icon: "blockquote", shortcut: "Ctrl+Shift+9" },
92
+ codeBlock: { name: "codeBlock", label: "코드 블록", icon: "codeBlock", shortcut: "Ctrl+Shift+C" },
93
+ link: { name: "link", label: "링크", icon: "link", shortcut: "Ctrl+K" },
94
+ image: { name: "image", label: "이미지", icon: "image" },
95
+ table: { name: "table", label: "표", icon: "table" },
96
+ hr: { name: "hr", label: "구분선", icon: "hr" },
97
+ clearFormat: { name: "clearFormat", label: "서식 지우기", icon: "clearFormat", shortcut: "Ctrl+\\" }
98
+ };
99
+ class Ve {
100
+ constructor(e, t) {
101
+ w(this, "element");
102
+ w(this, "buttons", /* @__PURE__ */ new Map());
103
+ this.element = document.createElement("div"), this.element.className = "aw-toolbar", this.element.setAttribute("role", "toolbar"), this.element.setAttribute("aria-label", "서식 도구 모음"), e.sticky !== !1 && this.element.classList.add("aw-toolbar-sticky");
104
+ let o = this.createGroup();
105
+ this.element.appendChild(o);
106
+ for (const l of e.items) {
107
+ if (l === "|") {
108
+ o = this.createGroup(), this.element.appendChild(o);
109
+ continue;
110
+ }
111
+ const i = Ye[l];
112
+ if (!i) continue;
113
+ const r = this.createButton(i, t);
114
+ this.buttons.set(i.name, r), o.appendChild(r);
115
+ }
116
+ }
117
+ setActive(e, t) {
118
+ const o = this.buttons.get(e);
119
+ o && (o.classList.toggle("aw-toolbar-btn-active", t), o.setAttribute("aria-pressed", String(t)));
120
+ }
121
+ setEnabled(e, t) {
122
+ const o = this.buttons.get(e);
123
+ o && (o.disabled = !t);
124
+ }
125
+ /** 조건부 항목을 완전히 숨긴다 (display:none). 허용 블록으로 돌아오면 다시 보임. */
126
+ setVisible(e, t) {
127
+ const o = this.buttons.get(e);
128
+ o && (o.style.display = t ? "" : "none");
129
+ }
130
+ /**
131
+ * 모든 자식 버튼이 숨겨진 그룹을 함께 숨긴다 — `|` 구분자 padding 이 외롭게
132
+ * 남지 않도록. applyToolbarState 에서 visibility 갱신 후 호출.
133
+ */
134
+ syncGroupVisibility() {
135
+ const e = this.element.querySelectorAll(".aw-toolbar-group");
136
+ for (const t of Array.from(e)) {
137
+ const o = Array.from(t.children).some(
138
+ (l) => l instanceof HTMLElement && l.style.display !== "none"
139
+ );
140
+ t.style.display = o ? "" : "none";
141
+ }
142
+ }
143
+ createGroup() {
144
+ const e = document.createElement("div");
145
+ return e.className = "aw-toolbar-group", e;
146
+ }
147
+ createButton(e, t) {
148
+ const o = document.createElement("button");
149
+ o.type = "button", o.className = "aw-toolbar-btn", e.hasDropdown && o.classList.add("aw-toolbar-btn-dropdown");
150
+ const l = e.shortcut ? `${e.label} (${e.shortcut})` : e.label;
151
+ o.title = l, o.setAttribute("aria-label", l), o.dataset.command = e.name;
152
+ const i = E[e.icon] ?? "";
153
+ return o.innerHTML = `<span class="aw-toolbar-btn-icon">${i}</span>`, e.hasDropdown && (o.innerHTML += `<span class="aw-toolbar-btn-chevron">${E.chevronDown}</span>`), o.addEventListener("mousedown", (r) => {
154
+ r.preventDefault();
155
+ }), o.addEventListener("click", () => {
156
+ t.onAction(e.name, o);
157
+ }), o;
158
+ }
159
+ }
160
+ function P(n, e) {
161
+ const t = document.createElement("div");
162
+ t.className = "aw-dialog-overlay", t.setAttribute("role", "dialog"), t.setAttribute("aria-modal", "true"), e && (t.dataset.theme = e);
163
+ const o = document.createElement("div");
164
+ o.className = "aw-dialog";
165
+ const l = document.createElement("div");
166
+ l.className = "aw-dialog-header";
167
+ const i = document.createElement("h2");
168
+ i.className = "aw-dialog-title", i.textContent = n;
169
+ const r = document.createElement("button");
170
+ r.type = "button", r.className = "aw-dialog-close", r.setAttribute("aria-label", "닫기"), r.innerHTML = E.close, l.appendChild(i), l.appendChild(r);
171
+ const s = document.createElement("div");
172
+ s.className = "aw-dialog-body";
173
+ const c = document.createElement("div");
174
+ c.className = "aw-dialog-footer", o.appendChild(l), o.appendChild(s), o.appendChild(c), t.appendChild(o);
175
+ const a = () => t.remove();
176
+ return r.addEventListener("click", a), t.addEventListener("click", (d) => {
177
+ d.target === t && a();
178
+ }), document.addEventListener(
179
+ "keydown",
180
+ function d(u) {
181
+ u.key === "Escape" && (a(), document.removeEventListener("keydown", d));
182
+ }
183
+ ), { overlay: t, body: s, footer: c, close: a };
184
+ }
185
+ function R(n, e) {
186
+ const t = document.createElement("div");
187
+ t.className = "aw-field";
188
+ const o = document.createElement("label");
189
+ return o.className = "aw-field-label", o.textContent = n, t.appendChild(o), t.appendChild(e), t;
190
+ }
191
+ function Ge(n = {}, e) {
192
+ return new Promise((t) => {
193
+ const { overlay: o, body: l, footer: i, close: r } = P("링크 삽입", e), s = document.createElement("input");
194
+ s.type = "text", s.className = "aw-input", s.placeholder = "링크에 표시할 텍스트", s.value = n.text ?? "";
195
+ const c = document.createElement("input");
196
+ c.type = "url", c.className = "aw-input", c.placeholder = "https://example.com", c.value = n.href ?? "";
197
+ const a = document.createElement("label");
198
+ a.className = "aw-checkbox";
199
+ const d = document.createElement("input");
200
+ d.type = "checkbox", d.checked = n.newWindow ?? !1;
201
+ const u = document.createElement("span");
202
+ u.textContent = "새 창에서 열기", a.appendChild(d), a.appendChild(u), l.appendChild(R("표시할 텍스트", s)), l.appendChild(R("URL", c)), l.appendChild(a);
203
+ const m = document.createElement("button");
204
+ m.type = "button", m.className = "aw-btn aw-btn-ghost", m.textContent = "취소";
205
+ const f = document.createElement("button");
206
+ f.type = "button", f.className = "aw-btn aw-btn-primary", f.textContent = "확인", i.appendChild(m), i.appendChild(f), m.addEventListener("click", () => {
207
+ r(), t({ confirmed: !1 });
208
+ }), f.addEventListener("click", () => {
209
+ r(), t({
210
+ confirmed: !0,
211
+ text: s.value,
212
+ href: c.value,
213
+ newWindow: d.checked
214
+ });
215
+ }), document.body.appendChild(o), setTimeout(() => c.focus(), 0);
216
+ });
217
+ }
218
+ function Ze(n = {}, e) {
219
+ return new Promise((t) => {
220
+ const { overlay: o, body: l, footer: i, close: r } = P("이미지", e), s = document.createElement("div");
221
+ s.className = "aw-tabs";
222
+ const c = document.createElement("button");
223
+ c.type = "button", c.className = "aw-tab", c.textContent = "임베드", c.title = "base64 데이터로 본문에 직접 삽입";
224
+ const a = document.createElement("button");
225
+ a.type = "button", a.className = "aw-tab", a.textContent = "URL", a.title = "이미지 URL 입력";
226
+ const d = document.createElement("button");
227
+ d.type = "button", d.className = "aw-tab", d.textContent = "업로드";
228
+ const u = !!n.onUpload;
229
+ u ? d.title = "서버 API 로 업로드 후 결과 URL 사용" : (d.disabled = !0, d.classList.add("aw-tab-disabled"), d.title = "개체 생성시 등록된 API 주소가 없습니다."), s.appendChild(c), s.appendChild(a), s.appendChild(d), l.appendChild(s);
230
+ const m = document.createElement("div");
231
+ m.className = "aw-pane";
232
+ const f = me("이미지 파일 선택 (base64 임베드)"), p = document.createElement("div");
233
+ p.className = "aw-preview", p.style.display = "none", m.appendChild(f.dropzone), m.appendChild(p);
234
+ const h = document.createElement("div");
235
+ h.className = "aw-pane";
236
+ const b = document.createElement("input");
237
+ b.type = "url", b.className = "aw-input", b.placeholder = "https://example.com/image.jpg", h.appendChild(R("이미지 URL", b));
238
+ const v = document.createElement("div");
239
+ if (v.className = "aw-pane", u) {
240
+ const g = me("서버 업로드용 이미지 선택"), x = document.createElement("div");
241
+ x.className = "aw-preview", x.style.display = "none", v.appendChild(g.dropzone), v.appendChild(x), g.input.addEventListener("change", async () => {
242
+ var $;
243
+ const B = ($ = g.input.files) == null ? void 0 : $[0];
244
+ if (!(!B || !n.onUpload))
245
+ try {
246
+ g.dropzone.classList.add("aw-dropzone-loading");
247
+ const K = await n.onUpload(B);
248
+ y = K, x.style.backgroundImage = `url(${JSON.stringify(K)})`, x.style.display = "block";
249
+ } catch (K) {
250
+ console.error(K);
251
+ } finally {
252
+ g.dropzone.classList.remove("aw-dropzone-loading"), g.input.value = "";
253
+ }
254
+ });
255
+ } else {
256
+ const g = document.createElement("div");
257
+ g.className = "aw-pane-notice", g.textContent = "개체 생성시 등록된 API 주소가 없습니다.", v.appendChild(g);
258
+ }
259
+ l.appendChild(m), l.appendChild(h), l.appendChild(v);
260
+ let k = "embed", T, y;
261
+ const L = (g) => {
262
+ g === "upload" && !u || (k = g, c.classList.toggle("aw-tab-active", g === "embed"), a.classList.toggle("aw-tab-active", g === "url"), d.classList.toggle("aw-tab-active", g === "upload"), m.style.display = g === "embed" ? "block" : "none", h.style.display = g === "url" ? "block" : "none", v.style.display = g === "upload" ? "block" : "none");
263
+ };
264
+ L(k), c.addEventListener("click", () => L("embed")), a.addEventListener("click", () => L("url")), d.addEventListener("click", () => L("upload")), f.input.addEventListener("change", () => {
265
+ var B;
266
+ const g = (B = f.input.files) == null ? void 0 : B[0];
267
+ if (!g) return;
268
+ const x = new FileReader();
269
+ x.onload = () => {
270
+ const $ = String(x.result ?? "");
271
+ T = $, p.style.backgroundImage = `url(${JSON.stringify($)})`, p.style.display = "block", f.input.value = "";
272
+ }, x.readAsDataURL(g);
273
+ });
274
+ const A = document.createElement("button");
275
+ A.type = "button", A.className = "aw-btn aw-btn-ghost", A.textContent = "취소";
276
+ const M = document.createElement("button");
277
+ M.type = "button", M.className = "aw-btn aw-btn-primary", M.textContent = "확인", i.appendChild(A), i.appendChild(M), A.addEventListener("click", () => {
278
+ r(), t({ confirmed: !1 });
279
+ }), M.addEventListener("click", () => {
280
+ let g;
281
+ k === "embed" ? g = T : k === "url" ? g = b.value || void 0 : k === "upload" && (g = y), r();
282
+ const x = { confirmed: !0 };
283
+ g && (x.src = g), t(x);
284
+ }), document.body.appendChild(o);
285
+ });
286
+ }
287
+ function me(n) {
288
+ const e = document.createElement("label");
289
+ e.className = "aw-dropzone", e.innerHTML = `
290
+ ${E.upload}
291
+ <div class="aw-dropzone-text">${n}<br/>드래그 앤 드롭 또는 클릭</div>
292
+ `;
293
+ const t = document.createElement("input");
294
+ return t.type = "file", t.accept = "image/*", t.style.display = "none", e.appendChild(t), { dropzone: e, input: t };
295
+ }
296
+ function Xe(n) {
297
+ return new Promise((e) => {
298
+ const { overlay: t, body: o, footer: l, close: i } = P("HTML", n), r = document.createElement("input");
299
+ r.type = "file", r.accept = ".html,.htm,text/html", r.style.display = "none";
300
+ const s = document.createElement("label");
301
+ s.className = "aw-dropzone", s.innerHTML = `
302
+ ${E.importHtml}
303
+ <div class="aw-dropzone-text">HTML 파일 선택<br/>드래그 앤 드롭 또는 클릭</div>
304
+ `, s.appendChild(r);
305
+ const c = document.createElement("div");
306
+ c.className = "aw-dialog-hint", c.textContent = "선택된 파일 없음";
307
+ const a = document.createElement("textarea");
308
+ a.className = "aw-input aw-textarea", a.placeholder = "또는 HTML 을 직접 붙여넣기…", a.rows = 8, o.appendChild(s), o.appendChild(c), o.appendChild(R("직접 붙여넣기", a));
309
+ const d = document.createElement("div");
310
+ d.className = "aw-dialog-hint", d.textContent = "iframe 으로 로드한 후 모든 스타일을 인라인으로 합쳐 가져옵니다.", o.appendChild(d);
311
+ let u;
312
+ r.addEventListener("change", () => {
313
+ var b;
314
+ const p = (b = r.files) == null ? void 0 : b[0];
315
+ if (!p) return;
316
+ const h = new FileReader();
317
+ h.onload = () => {
318
+ u = String(h.result ?? ""), a.value = u, c.textContent = `${p.name} (${p.size.toLocaleString()} bytes)`, r.value = "";
319
+ }, h.readAsText(p);
320
+ }), s.addEventListener("dragover", (p) => {
321
+ p.preventDefault(), s.classList.add("aw-dropzone-loading");
322
+ }), s.addEventListener("dragleave", () => {
323
+ s.classList.remove("aw-dropzone-loading");
324
+ }), s.addEventListener("drop", (p) => {
325
+ var v, k;
326
+ p.preventDefault(), s.classList.remove("aw-dropzone-loading");
327
+ const h = (k = (v = p.dataTransfer) == null ? void 0 : v.files) == null ? void 0 : k[0];
328
+ if (!h) return;
329
+ const b = new FileReader();
330
+ b.onload = () => {
331
+ u = String(b.result ?? ""), a.value = u, c.textContent = `${h.name} (${h.size.toLocaleString()} bytes)`;
332
+ }, b.readAsText(h);
333
+ });
334
+ const m = document.createElement("button");
335
+ m.type = "button", m.className = "aw-btn aw-btn-ghost", m.textContent = "취소";
336
+ const f = document.createElement("button");
337
+ f.type = "button", f.className = "aw-btn aw-btn-primary", f.textContent = "가져오기", l.appendChild(m), l.appendChild(f), m.addEventListener("click", () => {
338
+ i(), e({ confirmed: !1 });
339
+ }), f.addEventListener("click", () => {
340
+ const p = a.value.trim();
341
+ i();
342
+ const h = { confirmed: !0 };
343
+ p.length > 0 && (h.html = p), e(h);
344
+ }), document.body.appendChild(t);
345
+ });
346
+ }
347
+ function Qe(n) {
348
+ return new Promise((e) => {
349
+ const { overlay: t, body: o, footer: l, close: i } = P("Markdown", n), r = document.createElement("input");
350
+ r.type = "file", r.accept = ".md,.markdown,text/markdown,text/plain", r.style.display = "none";
351
+ const s = document.createElement("label");
352
+ s.className = "aw-dropzone", s.innerHTML = `
353
+ ${E.importMarkdown}
354
+ <div class="aw-dropzone-text">Markdown 파일 선택<br/>드래그 앤 드롭 또는 클릭</div>
355
+ `, s.appendChild(r);
356
+ const c = document.createElement("div");
357
+ c.className = "aw-dialog-hint", c.textContent = "선택된 파일 없음";
358
+ const a = document.createElement("textarea");
359
+ a.className = "aw-input aw-textarea", a.placeholder = "또는 Markdown 을 직접 붙여넣기…", a.rows = 8, o.appendChild(s), o.appendChild(c), o.appendChild(R("직접 붙여넣기", a));
360
+ const d = document.createElement("div");
361
+ d.className = "aw-dialog-hint", d.textContent = "CommonMark 기본 문법(제목/목록/링크/코드/표/인용 등) 지원", o.appendChild(d), r.addEventListener("change", () => {
362
+ var h;
363
+ const f = (h = r.files) == null ? void 0 : h[0];
364
+ if (!f) return;
365
+ const p = new FileReader();
366
+ p.onload = () => {
367
+ const b = String(p.result ?? "");
368
+ a.value = b, c.textContent = `${f.name} (${f.size.toLocaleString()} bytes)`, r.value = "";
369
+ }, p.readAsText(f);
370
+ }), s.addEventListener("dragover", (f) => {
371
+ f.preventDefault(), s.classList.add("aw-dropzone-loading");
372
+ }), s.addEventListener("dragleave", () => {
373
+ s.classList.remove("aw-dropzone-loading");
374
+ }), s.addEventListener("drop", (f) => {
375
+ var b, v;
376
+ f.preventDefault(), s.classList.remove("aw-dropzone-loading");
377
+ const p = (v = (b = f.dataTransfer) == null ? void 0 : b.files) == null ? void 0 : v[0];
378
+ if (!p) return;
379
+ const h = new FileReader();
380
+ h.onload = () => {
381
+ const k = String(h.result ?? "");
382
+ a.value = k, c.textContent = `${p.name} (${p.size.toLocaleString()} bytes)`;
383
+ }, h.readAsText(p);
384
+ });
385
+ const u = document.createElement("button");
386
+ u.type = "button", u.className = "aw-btn aw-btn-ghost", u.textContent = "취소";
387
+ const m = document.createElement("button");
388
+ m.type = "button", m.className = "aw-btn aw-btn-primary", m.textContent = "가져오기", l.appendChild(u), l.appendChild(m), u.addEventListener("click", () => {
389
+ i(), e({ confirmed: !1 });
390
+ }), m.addEventListener("click", () => {
391
+ const f = a.value.trim();
392
+ i();
393
+ const p = { confirmed: !0 };
394
+ f.length > 0 && (p.markdown = f), e(p);
395
+ }), document.body.appendChild(t);
396
+ });
397
+ }
398
+ function Je(n = {}, e) {
399
+ return new Promise((t) => {
400
+ const { overlay: o, body: l, footer: i, close: r } = P("파일 첨부", e), s = document.createElement("div");
401
+ s.className = "aw-tabs";
402
+ const c = document.createElement("button");
403
+ c.type = "button", c.className = "aw-tab", c.textContent = "URL", c.title = "이미 호스팅된 파일의 URL 직접 입력";
404
+ const a = document.createElement("button");
405
+ a.type = "button", a.className = "aw-tab", a.textContent = "업로드";
406
+ const d = !!n.onUpload;
407
+ d ? a.title = "서버 API 로 업로드 후 응답 URL 사용" : (a.disabled = !0, a.classList.add("aw-tab-disabled"), a.title = "개체 생성시 등록된 파일 업로드 API 가 없습니다."), s.appendChild(c), s.appendChild(a), l.appendChild(s);
408
+ const u = document.createElement("div");
409
+ u.className = "aw-pane";
410
+ const m = document.createElement("input");
411
+ m.type = "url", m.className = "aw-input", m.placeholder = "https://example.com/files/report.pdf", u.appendChild(R("파일 URL", m));
412
+ const f = document.createElement("input");
413
+ f.type = "text", f.className = "aw-input", f.placeholder = "표시할 파일명 (예: report.pdf)", u.appendChild(R("파일명", f));
414
+ const p = document.createElement("div");
415
+ p.className = "aw-pane";
416
+ let h;
417
+ if (d) {
418
+ const y = document.createElement("label");
419
+ y.className = "aw-dropzone", y.innerHTML = `
420
+ ${E.upload}
421
+ <div class="aw-dropzone-text">서버 업로드용 파일 선택<br/>드래그 앤 드롭 또는 클릭</div>
422
+ `;
423
+ const L = document.createElement("input");
424
+ L.type = "file", L.style.display = "none", y.appendChild(L);
425
+ const A = document.createElement("div");
426
+ A.className = "aw-dialog-hint", A.textContent = "선택된 파일 없음", p.appendChild(y), p.appendChild(A);
427
+ const M = async (g) => {
428
+ if (n.onUpload)
429
+ try {
430
+ y.classList.add("aw-dropzone-loading"), h = {
431
+ src: await n.onUpload(g),
432
+ name: g.name,
433
+ size: g.size,
434
+ type: g.type || ""
435
+ }, A.textContent = `${g.name} (${g.size.toLocaleString()} bytes) — 업로드 완료`;
436
+ } catch (x) {
437
+ console.error(x), A.textContent = `업로드 실패: ${x instanceof Error ? x.message : String(x)}`;
438
+ } finally {
439
+ y.classList.remove("aw-dropzone-loading"), L.value = "";
440
+ }
441
+ };
442
+ L.addEventListener("change", () => {
443
+ var x;
444
+ const g = (x = L.files) == null ? void 0 : x[0];
445
+ g && M(g);
446
+ }), y.addEventListener("dragover", (g) => {
447
+ g.preventDefault(), y.classList.add("aw-dropzone-loading");
448
+ }), y.addEventListener("dragleave", () => {
449
+ y.classList.remove("aw-dropzone-loading");
450
+ }), y.addEventListener("drop", (g) => {
451
+ var B, $;
452
+ g.preventDefault();
453
+ const x = ($ = (B = g.dataTransfer) == null ? void 0 : B.files) == null ? void 0 : $[0];
454
+ x && M(x);
455
+ });
456
+ } else {
457
+ const y = document.createElement("div");
458
+ y.className = "aw-pane-notice", y.textContent = "개체 생성시 등록된 파일 업로드 API 가 없습니다.", p.appendChild(y);
459
+ }
460
+ l.appendChild(u), l.appendChild(p);
461
+ let b = "url";
462
+ const v = (y) => {
463
+ y === "upload" && !d || (b = y, c.classList.toggle("aw-tab-active", y === "url"), a.classList.toggle("aw-tab-active", y === "upload"), u.style.display = y === "url" ? "block" : "none", p.style.display = y === "upload" ? "block" : "none");
464
+ };
465
+ v(b), c.addEventListener("click", () => v("url")), a.addEventListener("click", () => v("upload"));
466
+ const k = document.createElement("button");
467
+ k.type = "button", k.className = "aw-btn aw-btn-ghost", k.textContent = "취소";
468
+ const T = document.createElement("button");
469
+ T.type = "button", T.className = "aw-btn aw-btn-primary", T.textContent = "확인", i.appendChild(k), i.appendChild(T), k.addEventListener("click", () => {
470
+ r(), t({ confirmed: !1 });
471
+ }), T.addEventListener("click", () => {
472
+ const y = { confirmed: !0 };
473
+ if (b === "url") {
474
+ const L = m.value.trim(), A = f.value.trim();
475
+ L.length > 0 && (y.src = L), A.length > 0 && (y.name = A);
476
+ } else h && (y.src = h.src, y.name = h.name, y.size = h.size, h.type && (y.type = h.type));
477
+ r(), t(y);
478
+ }), document.body.appendChild(o);
479
+ });
480
+ }
481
+ function et(n) {
482
+ return new Promise((e) => {
483
+ const { overlay: t, body: o, footer: l, close: i } = P("유튜브 임베드", n), r = document.createElement("input");
484
+ r.type = "url", r.className = "aw-input", r.placeholder = "https://www.youtube.com/watch?v=…", o.appendChild(R("YouTube URL", r));
485
+ const s = document.createElement("div");
486
+ s.className = "aw-dialog-hint", s.textContent = "youtu.be / youtube.com/watch / /embed / /shorts 형식 모두 지원", o.appendChild(s);
487
+ const c = document.createElement("button");
488
+ c.type = "button", c.className = "aw-btn aw-btn-ghost", c.textContent = "취소";
489
+ const a = document.createElement("button");
490
+ a.type = "button", a.className = "aw-btn aw-btn-primary", a.textContent = "확인", l.appendChild(c), l.appendChild(a), c.addEventListener("click", () => {
491
+ i(), e({ confirmed: !1 });
492
+ }), a.addEventListener("click", () => {
493
+ i(), e({ confirmed: !0, url: r.value });
494
+ }), document.body.appendChild(t), setTimeout(() => r.focus(), 0);
495
+ });
496
+ }
497
+ const tt = "system", W = [
498
+ {
499
+ id: tt,
500
+ label: "시스템 기본",
501
+ stack: '-apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Pretendard", "Noto Sans KR", "Malgun Gothic", sans-serif'
502
+ },
503
+ {
504
+ id: "pretendard",
505
+ label: "Pretendard",
506
+ stack: '"Pretendard", -apple-system, sans-serif',
507
+ cssUrl: "https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.min.css"
508
+ },
509
+ {
510
+ id: "noto-sans-kr",
511
+ label: "Noto Sans KR",
512
+ stack: '"Noto Sans KR", sans-serif',
513
+ googleFonts: "Noto+Sans+KR:wght@400;700"
514
+ },
515
+ {
516
+ id: "noto-serif-kr",
517
+ label: "Noto Serif KR",
518
+ stack: '"Noto Serif KR", serif',
519
+ googleFonts: "Noto+Serif+KR:wght@400;700"
520
+ },
521
+ {
522
+ id: "nanum-gothic",
523
+ label: "나눔고딕",
524
+ stack: '"Nanum Gothic", sans-serif',
525
+ googleFonts: "Nanum+Gothic:wght@400;700"
526
+ },
527
+ {
528
+ id: "nanum-myeongjo",
529
+ label: "나눔명조",
530
+ stack: '"Nanum Myeongjo", serif',
531
+ googleFonts: "Nanum+Myeongjo:wght@400;700"
532
+ },
533
+ {
534
+ id: "nanum-pen",
535
+ label: "나눔펜",
536
+ stack: '"Nanum Pen Script", cursive',
537
+ googleFonts: "Nanum+Pen+Script"
538
+ },
539
+ {
540
+ id: "gowun-dodum",
541
+ label: "고운 돋움",
542
+ stack: '"Gowun Dodum", sans-serif',
543
+ googleFonts: "Gowun+Dodum"
544
+ },
545
+ {
546
+ id: "ibm-plex-sans-kr",
547
+ label: "IBM Plex Sans KR",
548
+ stack: '"IBM Plex Sans KR", sans-serif',
549
+ googleFonts: "IBM+Plex+Sans+KR:wght@400;700"
550
+ }
551
+ ];
552
+ (() => {
553
+ const n = {};
554
+ for (const e of W) n[e.id] = e;
555
+ return n;
556
+ })();
557
+ const nt = [
558
+ 12,
559
+ 13,
560
+ 14,
561
+ 15,
562
+ 16,
563
+ 18,
564
+ 20,
565
+ 24,
566
+ 28,
567
+ 32,
568
+ 40,
569
+ 48
570
+ ];
571
+ function ot(n) {
572
+ if (!n) return null;
573
+ const e = n.trim(), t = /^(\d+(?:\.\d+)?)px$/i.exec(e);
574
+ if (t && t[1]) return Math.round(Number(t[1]));
575
+ const o = /^(\d+(?:\.\d+)?)rem$/i.exec(e);
576
+ return o && o[1] ? Math.round(Number(o[1]) * 16) : null;
577
+ }
578
+ let fe = !1;
579
+ function lt() {
580
+ if (fe || (fe = !0, typeof document > "u")) return;
581
+ const n = W.map((e) => e.googleFonts).filter((e) => typeof e == "string" && e.length > 0);
582
+ if (n.length > 0) {
583
+ const e = "https://fonts.googleapis.com/css2?" + n.map((t) => `family=${t}`).join("&") + "&display=swap";
584
+ ge(e);
585
+ }
586
+ for (const e of W)
587
+ e.cssUrl && ge(e.cssUrl);
588
+ }
589
+ function ge(n) {
590
+ if (document.querySelector(
591
+ `link[rel="stylesheet"][href="${n}"]`
592
+ )) return;
593
+ const t = document.createElement("link");
594
+ t.rel = "stylesheet", t.href = n, document.head.appendChild(t);
595
+ }
596
+ function it(n) {
597
+ var o;
598
+ if (!n) return null;
599
+ const e = be(n);
600
+ if (!e) return null;
601
+ const t = e.toLowerCase();
602
+ for (const l of W) {
603
+ const i = (o = be(l.stack)) == null ? void 0 : o.toLowerCase();
604
+ if (i && i === t) return l.id;
605
+ }
606
+ return null;
607
+ }
608
+ function be(n) {
609
+ var t;
610
+ const e = (t = n.split(",")[0]) == null ? void 0 : t.trim();
611
+ return e ? e.replace(/^["']|["']$/g, "") : null;
612
+ }
613
+ const N = {
614
+ lineComment: "//",
615
+ blockComment: { start: "/*", end: "*/" },
616
+ stringDelims: ['"', "'"]
617
+ }, Te = /* @__PURE__ */ new Set([
618
+ "abstract",
619
+ "arguments",
620
+ "async",
621
+ "await",
622
+ "break",
623
+ "case",
624
+ "catch",
625
+ "class",
626
+ "const",
627
+ "continue",
628
+ "debugger",
629
+ "default",
630
+ "delete",
631
+ "do",
632
+ "else",
633
+ "enum",
634
+ "export",
635
+ "extends",
636
+ "finally",
637
+ "for",
638
+ "from",
639
+ "function",
640
+ "get",
641
+ "if",
642
+ "implements",
643
+ "import",
644
+ "in",
645
+ "instanceof",
646
+ "interface",
647
+ "let",
648
+ "new",
649
+ "null",
650
+ "of",
651
+ "package",
652
+ "private",
653
+ "protected",
654
+ "public",
655
+ "return",
656
+ "set",
657
+ "static",
658
+ "super",
659
+ "switch",
660
+ "this",
661
+ "throw",
662
+ "true",
663
+ "false",
664
+ "try",
665
+ "typeof",
666
+ "undefined",
667
+ "var",
668
+ "void",
669
+ "while",
670
+ "with",
671
+ "yield"
672
+ ]), rt = /* @__PURE__ */ new Set([
673
+ ...Te,
674
+ "any",
675
+ "as",
676
+ "asserts",
677
+ "boolean",
678
+ "declare",
679
+ "infer",
680
+ "is",
681
+ "keyof",
682
+ "module",
683
+ "namespace",
684
+ "never",
685
+ "number",
686
+ "object",
687
+ "override",
688
+ "readonly",
689
+ "require",
690
+ "satisfies",
691
+ "string",
692
+ "symbol",
693
+ "type",
694
+ "unique",
695
+ "unknown"
696
+ ]), st = /* @__PURE__ */ new Set([
697
+ "False",
698
+ "None",
699
+ "True",
700
+ "and",
701
+ "as",
702
+ "assert",
703
+ "async",
704
+ "await",
705
+ "break",
706
+ "class",
707
+ "continue",
708
+ "def",
709
+ "del",
710
+ "elif",
711
+ "else",
712
+ "except",
713
+ "finally",
714
+ "for",
715
+ "from",
716
+ "global",
717
+ "if",
718
+ "import",
719
+ "in",
720
+ "is",
721
+ "lambda",
722
+ "nonlocal",
723
+ "not",
724
+ "or",
725
+ "pass",
726
+ "raise",
727
+ "return",
728
+ "try",
729
+ "while",
730
+ "with",
731
+ "yield"
732
+ ]), at = /* @__PURE__ */ new Set([
733
+ "abs",
734
+ "all",
735
+ "any",
736
+ "bool",
737
+ "dict",
738
+ "enumerate",
739
+ "filter",
740
+ "float",
741
+ "int",
742
+ "isinstance",
743
+ "len",
744
+ "list",
745
+ "map",
746
+ "max",
747
+ "min",
748
+ "open",
749
+ "print",
750
+ "range",
751
+ "set",
752
+ "sorted",
753
+ "str",
754
+ "sum",
755
+ "tuple",
756
+ "type",
757
+ "zip",
758
+ "self",
759
+ "cls"
760
+ ]), Ne = /* @__PURE__ */ new Set([
761
+ "auto",
762
+ "break",
763
+ "case",
764
+ "char",
765
+ "const",
766
+ "continue",
767
+ "default",
768
+ "do",
769
+ "double",
770
+ "else",
771
+ "enum",
772
+ "extern",
773
+ "float",
774
+ "for",
775
+ "goto",
776
+ "if",
777
+ "int",
778
+ "long",
779
+ "register",
780
+ "return",
781
+ "short",
782
+ "signed",
783
+ "sizeof",
784
+ "static",
785
+ "struct",
786
+ "switch",
787
+ "typedef",
788
+ "union",
789
+ "unsigned",
790
+ "void",
791
+ "volatile",
792
+ "while",
793
+ "inline",
794
+ "restrict",
795
+ "_Bool",
796
+ "NULL"
797
+ ]), ct = /* @__PURE__ */ new Set([
798
+ ...Ne,
799
+ "class",
800
+ "new",
801
+ "delete",
802
+ "try",
803
+ "catch",
804
+ "throw",
805
+ "template",
806
+ "typename",
807
+ "namespace",
808
+ "using",
809
+ "public",
810
+ "private",
811
+ "protected",
812
+ "virtual",
813
+ "override",
814
+ "this",
815
+ "nullptr",
816
+ "true",
817
+ "false",
818
+ "bool",
819
+ "explicit",
820
+ "friend",
821
+ "mutable",
822
+ "operator",
823
+ "reinterpret_cast",
824
+ "static_cast",
825
+ "dynamic_cast",
826
+ "const_cast",
827
+ "constexpr",
828
+ "decltype",
829
+ "noexcept",
830
+ "final",
831
+ "auto"
832
+ ]), dt = /* @__PURE__ */ new Set([
833
+ "abstract",
834
+ "async",
835
+ "await",
836
+ "base",
837
+ "bool",
838
+ "break",
839
+ "byte",
840
+ "case",
841
+ "catch",
842
+ "char",
843
+ "checked",
844
+ "class",
845
+ "const",
846
+ "continue",
847
+ "decimal",
848
+ "default",
849
+ "delegate",
850
+ "do",
851
+ "double",
852
+ "else",
853
+ "enum",
854
+ "event",
855
+ "explicit",
856
+ "extern",
857
+ "false",
858
+ "finally",
859
+ "fixed",
860
+ "float",
861
+ "for",
862
+ "foreach",
863
+ "goto",
864
+ "if",
865
+ "implicit",
866
+ "in",
867
+ "int",
868
+ "interface",
869
+ "internal",
870
+ "is",
871
+ "lock",
872
+ "long",
873
+ "namespace",
874
+ "new",
875
+ "null",
876
+ "object",
877
+ "operator",
878
+ "out",
879
+ "override",
880
+ "params",
881
+ "private",
882
+ "protected",
883
+ "public",
884
+ "readonly",
885
+ "ref",
886
+ "return",
887
+ "sbyte",
888
+ "sealed",
889
+ "short",
890
+ "sizeof",
891
+ "stackalloc",
892
+ "static",
893
+ "string",
894
+ "struct",
895
+ "switch",
896
+ "this",
897
+ "throw",
898
+ "true",
899
+ "try",
900
+ "typeof",
901
+ "uint",
902
+ "ulong",
903
+ "unchecked",
904
+ "unsafe",
905
+ "ushort",
906
+ "using",
907
+ "var",
908
+ "virtual",
909
+ "void",
910
+ "volatile",
911
+ "while",
912
+ "yield",
913
+ "record",
914
+ "init",
915
+ "nameof",
916
+ "when"
917
+ ]), ut = /* @__PURE__ */ new Set([
918
+ "abstract",
919
+ "assert",
920
+ "boolean",
921
+ "break",
922
+ "byte",
923
+ "case",
924
+ "catch",
925
+ "char",
926
+ "class",
927
+ "const",
928
+ "continue",
929
+ "default",
930
+ "do",
931
+ "double",
932
+ "else",
933
+ "enum",
934
+ "extends",
935
+ "final",
936
+ "finally",
937
+ "float",
938
+ "for",
939
+ "goto",
940
+ "if",
941
+ "implements",
942
+ "import",
943
+ "instanceof",
944
+ "int",
945
+ "interface",
946
+ "long",
947
+ "native",
948
+ "new",
949
+ "null",
950
+ "package",
951
+ "private",
952
+ "protected",
953
+ "public",
954
+ "return",
955
+ "short",
956
+ "static",
957
+ "strictfp",
958
+ "super",
959
+ "switch",
960
+ "synchronized",
961
+ "this",
962
+ "throw",
963
+ "throws",
964
+ "transient",
965
+ "true",
966
+ "false",
967
+ "try",
968
+ "void",
969
+ "volatile",
970
+ "while",
971
+ "var",
972
+ "yield",
973
+ "record",
974
+ "sealed",
975
+ "permits",
976
+ "non-sealed"
977
+ ]), pt = /* @__PURE__ */ new Set([
978
+ "abstract",
979
+ "actual",
980
+ "annotation",
981
+ "as",
982
+ "break",
983
+ "by",
984
+ "catch",
985
+ "class",
986
+ "companion",
987
+ "const",
988
+ "constructor",
989
+ "continue",
990
+ "crossinline",
991
+ "data",
992
+ "do",
993
+ "dynamic",
994
+ "else",
995
+ "enum",
996
+ "expect",
997
+ "external",
998
+ "false",
999
+ "final",
1000
+ "finally",
1001
+ "for",
1002
+ "fun",
1003
+ "get",
1004
+ "if",
1005
+ "import",
1006
+ "in",
1007
+ "init",
1008
+ "inline",
1009
+ "inner",
1010
+ "interface",
1011
+ "internal",
1012
+ "is",
1013
+ "it",
1014
+ "lateinit",
1015
+ "null",
1016
+ "object",
1017
+ "open",
1018
+ "operator",
1019
+ "out",
1020
+ "override",
1021
+ "package",
1022
+ "private",
1023
+ "protected",
1024
+ "public",
1025
+ "reified",
1026
+ "return",
1027
+ "sealed",
1028
+ "set",
1029
+ "super",
1030
+ "suspend",
1031
+ "tailrec",
1032
+ "this",
1033
+ "throw",
1034
+ "true",
1035
+ "try",
1036
+ "typealias",
1037
+ "typeof",
1038
+ "val",
1039
+ "var",
1040
+ "vararg",
1041
+ "when",
1042
+ "where",
1043
+ "while"
1044
+ ]), ht = /* @__PURE__ */ new Set([
1045
+ "break",
1046
+ "case",
1047
+ "chan",
1048
+ "const",
1049
+ "continue",
1050
+ "default",
1051
+ "defer",
1052
+ "else",
1053
+ "fallthrough",
1054
+ "for",
1055
+ "func",
1056
+ "go",
1057
+ "goto",
1058
+ "if",
1059
+ "import",
1060
+ "interface",
1061
+ "map",
1062
+ "package",
1063
+ "range",
1064
+ "return",
1065
+ "select",
1066
+ "struct",
1067
+ "switch",
1068
+ "type",
1069
+ "var",
1070
+ "true",
1071
+ "false",
1072
+ "nil",
1073
+ "iota"
1074
+ ]), mt = /* @__PURE__ */ new Set([
1075
+ "as",
1076
+ "async",
1077
+ "await",
1078
+ "break",
1079
+ "const",
1080
+ "continue",
1081
+ "crate",
1082
+ "dyn",
1083
+ "else",
1084
+ "enum",
1085
+ "extern",
1086
+ "false",
1087
+ "fn",
1088
+ "for",
1089
+ "if",
1090
+ "impl",
1091
+ "in",
1092
+ "let",
1093
+ "loop",
1094
+ "match",
1095
+ "mod",
1096
+ "move",
1097
+ "mut",
1098
+ "pub",
1099
+ "ref",
1100
+ "return",
1101
+ "self",
1102
+ "Self",
1103
+ "static",
1104
+ "struct",
1105
+ "super",
1106
+ "trait",
1107
+ "true",
1108
+ "type",
1109
+ "unsafe",
1110
+ "use",
1111
+ "where",
1112
+ "while",
1113
+ "box",
1114
+ "do",
1115
+ "final",
1116
+ "macro",
1117
+ "override",
1118
+ "priv",
1119
+ "typeof",
1120
+ "unsized",
1121
+ "virtual",
1122
+ "yield"
1123
+ ]), ft = /* @__PURE__ */ new Set([
1124
+ "if",
1125
+ "then",
1126
+ "else",
1127
+ "elif",
1128
+ "fi",
1129
+ "case",
1130
+ "esac",
1131
+ "for",
1132
+ "while",
1133
+ "until",
1134
+ "do",
1135
+ "done",
1136
+ "function",
1137
+ "return",
1138
+ "select",
1139
+ "break",
1140
+ "continue",
1141
+ "in",
1142
+ "echo",
1143
+ "export",
1144
+ "local",
1145
+ "readonly",
1146
+ "declare",
1147
+ "typeset",
1148
+ "unset",
1149
+ "eval",
1150
+ "exec",
1151
+ "source",
1152
+ "true",
1153
+ "false"
1154
+ ]), gt = /* @__PURE__ */ new Set([
1155
+ "select",
1156
+ "from",
1157
+ "where",
1158
+ "insert",
1159
+ "update",
1160
+ "delete",
1161
+ "into",
1162
+ "values",
1163
+ "set",
1164
+ "create",
1165
+ "table",
1166
+ "drop",
1167
+ "alter",
1168
+ "index",
1169
+ "view",
1170
+ "join",
1171
+ "inner",
1172
+ "left",
1173
+ "right",
1174
+ "outer",
1175
+ "full",
1176
+ "cross",
1177
+ "on",
1178
+ "group",
1179
+ "by",
1180
+ "order",
1181
+ "having",
1182
+ "limit",
1183
+ "offset",
1184
+ "union",
1185
+ "all",
1186
+ "distinct",
1187
+ "as",
1188
+ "and",
1189
+ "or",
1190
+ "not",
1191
+ "null",
1192
+ "true",
1193
+ "false",
1194
+ "is",
1195
+ "in",
1196
+ "between",
1197
+ "like",
1198
+ "exists",
1199
+ "primary",
1200
+ "key",
1201
+ "foreign",
1202
+ "references",
1203
+ "unique",
1204
+ "default",
1205
+ "check",
1206
+ "constraint",
1207
+ "database",
1208
+ "schema",
1209
+ "with",
1210
+ "case",
1211
+ "when",
1212
+ "then",
1213
+ "else",
1214
+ "end",
1215
+ "cast",
1216
+ "count",
1217
+ "sum",
1218
+ "avg",
1219
+ "min",
1220
+ "max",
1221
+ "asc",
1222
+ "desc"
1223
+ ]), bt = /* @__PURE__ */ new Set(["true", "false", "null"]), Me = [
1224
+ {
1225
+ id: "plaintext",
1226
+ label: "Plain Text",
1227
+ keywords: /* @__PURE__ */ new Set()
1228
+ },
1229
+ {
1230
+ id: "javascript",
1231
+ label: "JavaScript",
1232
+ keywords: Te,
1233
+ ...N,
1234
+ stringDelims: ['"', "'", "`"]
1235
+ },
1236
+ {
1237
+ id: "typescript",
1238
+ label: "TypeScript",
1239
+ keywords: rt,
1240
+ ...N,
1241
+ stringDelims: ['"', "'", "`"]
1242
+ },
1243
+ {
1244
+ id: "python",
1245
+ label: "Python",
1246
+ keywords: st,
1247
+ builtins: at,
1248
+ lineComment: "#",
1249
+ stringDelims: ['"', "'"]
1250
+ },
1251
+ {
1252
+ id: "json",
1253
+ label: "JSON",
1254
+ keywords: bt,
1255
+ stringDelims: ['"']
1256
+ },
1257
+ {
1258
+ id: "html",
1259
+ label: "HTML",
1260
+ keywords: /* @__PURE__ */ new Set(),
1261
+ custom: (n) => Ct(n)
1262
+ },
1263
+ {
1264
+ id: "css",
1265
+ label: "CSS",
1266
+ keywords: /* @__PURE__ */ new Set(),
1267
+ custom: (n) => Et(n)
1268
+ },
1269
+ {
1270
+ id: "bash",
1271
+ label: "Bash",
1272
+ keywords: ft,
1273
+ lineComment: "#",
1274
+ stringDelims: ['"', "'"]
1275
+ },
1276
+ {
1277
+ id: "sql",
1278
+ label: "SQL",
1279
+ keywords: gt,
1280
+ caseInsensitiveKeywords: !0,
1281
+ lineComment: "--",
1282
+ blockComment: { start: "/*", end: "*/" },
1283
+ stringDelims: ['"', "'"]
1284
+ },
1285
+ {
1286
+ id: "markdown",
1287
+ label: "Markdown",
1288
+ keywords: /* @__PURE__ */ new Set(),
1289
+ custom: (n) => Lt(n)
1290
+ },
1291
+ { id: "c", label: "C", keywords: Ne, ...N },
1292
+ { id: "cpp", label: "C++", keywords: ct, ...N },
1293
+ { id: "csharp", label: "C#", keywords: dt, ...N },
1294
+ { id: "java", label: "Java", keywords: ut, ...N },
1295
+ { id: "kotlin", label: "Kotlin", keywords: pt, ...N },
1296
+ { id: "go", label: "Go", keywords: ht, ...N },
1297
+ { id: "rust", label: "Rust", keywords: mt, ...N }
1298
+ ], yt = (() => {
1299
+ const n = /* @__PURE__ */ new Map();
1300
+ for (const e of Me) n.set(e.id, e);
1301
+ return n;
1302
+ })();
1303
+ function wt(n, e) {
1304
+ if (!e) return C(n);
1305
+ const t = yt.get(e);
1306
+ return t ? t.custom ? t.custom(n) : kt(n, t) : C(n);
1307
+ }
1308
+ function kt(n, e) {
1309
+ var r, s;
1310
+ const t = !!e.caseInsensitiveKeywords;
1311
+ let o = "", l = 0;
1312
+ const i = n.length;
1313
+ for (; l < i; ) {
1314
+ if (e.lineComment && n.startsWith(e.lineComment, l)) {
1315
+ const d = n.indexOf(`
1316
+ `, l), u = d === -1 ? i : d;
1317
+ o += `<span class="hl-comment">${C(n.slice(l, u))}</span>`, l = u;
1318
+ continue;
1319
+ }
1320
+ if (e.blockComment && n.startsWith(e.blockComment.start, l)) {
1321
+ const d = n.indexOf(e.blockComment.end, l + e.blockComment.start.length), u = d === -1 ? i : d + e.blockComment.end.length;
1322
+ o += `<span class="hl-comment">${C(n.slice(l, u))}</span>`, l = u;
1323
+ continue;
1324
+ }
1325
+ const c = (r = e.stringDelims) == null ? void 0 : r.find((d) => n.startsWith(d, l));
1326
+ if (c) {
1327
+ let d = l + c.length;
1328
+ for (; d < i; ) {
1329
+ const u = n[d] ?? "";
1330
+ if (u === "\\") {
1331
+ d += 2;
1332
+ continue;
1333
+ }
1334
+ if (n.startsWith(c, d)) {
1335
+ d += c.length;
1336
+ break;
1337
+ }
1338
+ if (u === `
1339
+ ` && c !== "`") break;
1340
+ d++;
1341
+ }
1342
+ o += `<span class="hl-string">${C(n.slice(l, d))}</span>`, l = d;
1343
+ continue;
1344
+ }
1345
+ const a = n[l] ?? "";
1346
+ if (/[0-9]/.test(a)) {
1347
+ const d = vt.exec(n.slice(l));
1348
+ if (d) {
1349
+ o += `<span class="hl-number">${C(d[0])}</span>`, l += d[0].length;
1350
+ continue;
1351
+ }
1352
+ }
1353
+ if (/[a-zA-Z_$]/.test(a)) {
1354
+ let d = l + 1;
1355
+ for (; d < i && /[a-zA-Z0-9_$]/.test(n[d] ?? ""); ) d++;
1356
+ const u = n.slice(l, d), m = t ? u.toLowerCase() : u;
1357
+ if (e.keywords.has(m))
1358
+ o += `<span class="hl-keyword">${C(u)}</span>`;
1359
+ else if ((s = e.builtins) != null && s.has(m))
1360
+ o += `<span class="hl-builtin">${C(u)}</span>`;
1361
+ else {
1362
+ let f = d;
1363
+ for (; f < i && (n[f] === " " || n[f] === " "); ) f++;
1364
+ n[f] === "(" ? o += `<span class="hl-fn">${C(u)}</span>` : o += C(u);
1365
+ }
1366
+ l = d;
1367
+ continue;
1368
+ }
1369
+ o += C(a), l++;
1370
+ }
1371
+ return o;
1372
+ }
1373
+ const vt = /^(0[xX][0-9a-fA-F_]+|0[bB][01_]+|0[oO][0-7_]+|[0-9][0-9_]*(?:\.[0-9_]+)?(?:[eE][-+]?[0-9_]+)?)[a-zA-Z]*/;
1374
+ function Ct(n) {
1375
+ let e = "", t = 0;
1376
+ const o = n.length;
1377
+ for (; t < o; ) {
1378
+ if (n.startsWith("<!--", t)) {
1379
+ const l = n.indexOf("-->", t + 4), i = l === -1 ? o : l + 3;
1380
+ e += `<span class="hl-comment">${C(n.slice(t, i))}</span>`, t = i;
1381
+ continue;
1382
+ }
1383
+ if (n[t] === "<") {
1384
+ const l = n.indexOf(">", t);
1385
+ if (l === -1) {
1386
+ e += C(n.slice(t));
1387
+ break;
1388
+ }
1389
+ const i = n.slice(t, l + 1);
1390
+ e += xt(i), t = l + 1;
1391
+ continue;
1392
+ }
1393
+ e += C(n[t] ?? ""), t++;
1394
+ }
1395
+ return e;
1396
+ }
1397
+ function xt(n) {
1398
+ const e = n.slice(1, -1);
1399
+ let t = '<span class="hl-punct">&lt;</span>';
1400
+ const o = /^\/?[a-zA-Z][a-zA-Z0-9-]*/.exec(e);
1401
+ if (o) {
1402
+ t += `<span class="hl-tag">${C(o[0])}</span>`;
1403
+ let l = e.slice(o[0].length);
1404
+ const i = /\s+([a-zA-Z_:][a-zA-Z0-9_:.-]*)(\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
1405
+ let r = 0, s;
1406
+ for (; s = i.exec(l); ) {
1407
+ t += C(l.slice(r, s.index)), t += C(l.slice(s.index, s.index + s[0].indexOf(s[1] ?? ""))), t += `<span class="hl-attr">${C(s[1] ?? "")}</span>`;
1408
+ const c = l.slice(s.index + (s[0].indexOf(s[1] ?? "") + (s[1] ?? "").length), s.index + s[0].length);
1409
+ if (c.includes("=")) {
1410
+ const a = c.indexOf("=");
1411
+ t += C(c.slice(0, a + 1));
1412
+ const d = c.slice(a + 1);
1413
+ t += `<span class="hl-string">${C(d)}</span>`;
1414
+ }
1415
+ r = s.index + s[0].length;
1416
+ }
1417
+ t += C(l.slice(r));
1418
+ } else
1419
+ t += C(e);
1420
+ return t += '<span class="hl-punct">&gt;</span>', t;
1421
+ }
1422
+ function Et(n) {
1423
+ let e = "", t = 0;
1424
+ const o = n.length;
1425
+ let l = !1;
1426
+ for (; t < o; ) {
1427
+ if (n.startsWith("/*", t)) {
1428
+ const r = n.indexOf("*/", t + 2), s = r === -1 ? o : r + 2;
1429
+ e += `<span class="hl-comment">${C(n.slice(t, s))}</span>`, t = s;
1430
+ continue;
1431
+ }
1432
+ const i = n[t] ?? "";
1433
+ if (i === "{") {
1434
+ l = !0, e += C(i), t++;
1435
+ continue;
1436
+ }
1437
+ if (i === "}") {
1438
+ l = !1, e += C(i), t++;
1439
+ continue;
1440
+ }
1441
+ if (i === '"' || i === "'") {
1442
+ const r = i;
1443
+ let s = t + 1;
1444
+ for (; s < o && n[s] !== r; )
1445
+ n[s] === "\\" ? s += 2 : s++;
1446
+ s = Math.min(s + 1, o), e += `<span class="hl-string">${C(n.slice(t, s))}</span>`, t = s;
1447
+ continue;
1448
+ }
1449
+ if (/[a-zA-Z_-]/.test(i) || i === "@" || i === "#" || i === "." || i === ":") {
1450
+ let r = t + 1;
1451
+ for (; r < o && /[a-zA-Z0-9_-]/.test(n[r] ?? ""); ) r++;
1452
+ const s = n.slice(t, r);
1453
+ if (i === "@")
1454
+ e += `<span class="hl-keyword">${C(s)}</span>`;
1455
+ else if (l && /^[a-zA-Z-]+$/.test(s)) {
1456
+ let c = r;
1457
+ for (; c < o && (n[c] === " " || n[c] === " "); ) c++;
1458
+ n[c] === ":" ? e += `<span class="hl-prop">${C(s)}</span>` : e += C(s);
1459
+ } else
1460
+ e += `<span class="hl-tag">${C(s)}</span>`;
1461
+ t = r;
1462
+ continue;
1463
+ }
1464
+ if (/[0-9]/.test(i)) {
1465
+ const r = /^[0-9.]+(px|em|rem|%|vh|vw|s|ms|deg|fr)?/.exec(n.slice(t));
1466
+ if (r) {
1467
+ e += `<span class="hl-number">${C(r[0])}</span>`, t += r[0].length;
1468
+ continue;
1469
+ }
1470
+ }
1471
+ e += C(i), t++;
1472
+ }
1473
+ return e;
1474
+ }
1475
+ function Lt(n) {
1476
+ return n.split(`
1477
+ `).map(At).join(`
1478
+ `);
1479
+ }
1480
+ function At(n) {
1481
+ const e = /^(#{1,6})\s+(.*)$/.exec(n);
1482
+ if (e)
1483
+ return `<span class="hl-keyword">${C(e[1] ?? "")}</span> <span class="hl-tag">${ee(e[2] ?? "")}</span>`;
1484
+ if (n.startsWith(">"))
1485
+ return `<span class="hl-comment">${C(n)}</span>`;
1486
+ if (n.startsWith("```"))
1487
+ return `<span class="hl-keyword">${C(n)}</span>`;
1488
+ const t = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(n);
1489
+ return t ? `${t[1] ?? ""}<span class="hl-keyword">${C(t[2] ?? "")}</span> ${ee(t[3] ?? "")}` : ee(n);
1490
+ }
1491
+ function ee(n) {
1492
+ let e = C(n);
1493
+ return e = e.replace(/\*\*([^*]+)\*\*/g, '<span class="hl-keyword">**$1**</span>'), e = e.replace(new RegExp("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "g"), '<span class="hl-string">*$1*</span>'), e = e.replace(/`([^`]+)`/g, '<span class="hl-builtin">`$1`</span>'), e = e.replace(
1494
+ /\[([^\]]+)\]\(([^)]+)\)/g,
1495
+ '<span class="hl-tag">[$1]</span><span class="hl-string">($2)</span>'
1496
+ ), e;
1497
+ }
1498
+ function C(n) {
1499
+ return n.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1500
+ }
1501
+ function S(n) {
1502
+ n.addEventListener("mousedown", (e) => e.preventDefault());
1503
+ }
1504
+ const ae = [
1505
+ "#000000",
1506
+ "#5c5c5c",
1507
+ "#9c9c9c",
1508
+ "#cfcfcf",
1509
+ "#ffffff",
1510
+ "#e03131",
1511
+ "#f76707",
1512
+ "#f59f00",
1513
+ "#94d82d",
1514
+ "#0ca678",
1515
+ "#1c7ed6",
1516
+ "#7048e8",
1517
+ "#d6336c",
1518
+ "#fab005",
1519
+ "#37b24d",
1520
+ "#1098ad"
1521
+ ];
1522
+ function te(n, e, t = []) {
1523
+ z();
1524
+ const o = document.createElement("div");
1525
+ o.className = "aw-popover aw-color-popover", o.dataset.popover = "true", S(o);
1526
+ const l = (d) => {
1527
+ e(d), o.remove();
1528
+ }, i = (d) => {
1529
+ const u = document.createElement("button");
1530
+ return u.type = "button", u.className = "aw-color-swatch", u.style.backgroundColor = d, u.setAttribute("aria-label", d), u.title = d, S(u), u.addEventListener("click", (m) => {
1531
+ m.stopPropagation(), l(d);
1532
+ }), u;
1533
+ }, r = document.createElement("div");
1534
+ r.className = "aw-color-grid";
1535
+ for (const d of ae)
1536
+ r.appendChild(i(d));
1537
+ o.appendChild(r);
1538
+ const s = document.createElement("button");
1539
+ s.type = "button", s.className = "aw-popover-action", s.textContent = "기본색", S(s), s.addEventListener("click", (d) => {
1540
+ d.stopPropagation(), l(null);
1541
+ });
1542
+ const c = document.createElement("button");
1543
+ c.type = "button", c.className = "aw-popover-action", c.textContent = "사용자 지정", S(c);
1544
+ const a = document.createElement("input");
1545
+ if (a.type = "color", a.tabIndex = -1, a.setAttribute("aria-hidden", "true"), a.style.position = "absolute", a.style.left = "-9999px", a.style.width = "0", a.style.height = "0", a.style.opacity = "0", a.style.pointerEvents = "none", a.addEventListener("change", () => {
1546
+ l(a.value);
1547
+ }), c.addEventListener("click", (d) => {
1548
+ d.stopPropagation(), a.click();
1549
+ }), o.appendChild(s), o.appendChild(c), o.appendChild(a), t.length > 0) {
1550
+ const d = document.createElement("div");
1551
+ d.className = "aw-color-section", d.textContent = "최근 사용", o.appendChild(d);
1552
+ const u = document.createElement("div");
1553
+ u.className = "aw-color-grid";
1554
+ for (const m of t)
1555
+ u.appendChild(i(m));
1556
+ o.appendChild(u);
1557
+ }
1558
+ I(o, n);
1559
+ }
1560
+ const St = [
1561
+ { label: "본문", kind: "paragraph", attrs: {}, glyph: "¶" },
1562
+ { label: "제목 1", kind: "heading", attrs: { level: 1 }, glyph: "H1", weight: "700" },
1563
+ { label: "제목 2", kind: "heading", attrs: { level: 2 }, glyph: "H2", weight: "700" },
1564
+ { label: "제목 3", kind: "heading", attrs: { level: 3 }, glyph: "H3", weight: "700" },
1565
+ {
1566
+ label: "글머리 목록",
1567
+ kind: "bulletList",
1568
+ attrs: {},
1569
+ glyph: E.bulletList,
1570
+ glyphIsSvg: !0,
1571
+ divider: !0
1572
+ },
1573
+ {
1574
+ label: "번호 매기기 목록",
1575
+ kind: "orderedList",
1576
+ attrs: {},
1577
+ glyph: E.orderedList,
1578
+ glyphIsSvg: !0
1579
+ },
1580
+ {
1581
+ label: "인용구",
1582
+ kind: "blockquote",
1583
+ attrs: {},
1584
+ glyph: E.blockquote,
1585
+ glyphIsSvg: !0,
1586
+ divider: !0
1587
+ },
1588
+ {
1589
+ label: "코드 블록",
1590
+ kind: "codeBlock",
1591
+ attrs: {},
1592
+ glyph: E.codeBlock,
1593
+ glyphIsSvg: !0
1594
+ },
1595
+ // ── 리치 블록 그룹 (링크/이미지/표/유튜브/파일/구분선) ────────────────
1596
+ // 구분선은 단독 행 회피를 위해 리치 그룹 끝에서 파일 첨부와 짝지어 배치.
1597
+ {
1598
+ label: "링크",
1599
+ kind: "linkCard",
1600
+ attrs: {},
1601
+ glyph: E.link,
1602
+ glyphIsSvg: !0,
1603
+ divider: !0
1604
+ },
1605
+ {
1606
+ label: "이미지",
1607
+ kind: "image",
1608
+ attrs: {},
1609
+ glyph: E.image,
1610
+ glyphIsSvg: !0
1611
+ },
1612
+ {
1613
+ label: "표",
1614
+ kind: "table",
1615
+ attrs: {},
1616
+ glyph: E.table,
1617
+ glyphIsSvg: !0
1618
+ },
1619
+ {
1620
+ label: "유튜브",
1621
+ kind: "youtube",
1622
+ attrs: {},
1623
+ glyph: E.youtube,
1624
+ glyphIsSvg: !0
1625
+ },
1626
+ {
1627
+ label: "파일 첨부",
1628
+ kind: "file",
1629
+ attrs: {},
1630
+ glyph: E.attachFile,
1631
+ glyphIsSvg: !0
1632
+ },
1633
+ {
1634
+ label: "구분선",
1635
+ kind: "hr",
1636
+ attrs: {},
1637
+ glyph: E.hr,
1638
+ glyphIsSvg: !0
1639
+ },
1640
+ // ── 외부 소스 가져오기 ─────────────────────────────────────────────
1641
+ {
1642
+ label: "HTML",
1643
+ kind: "importHtml",
1644
+ attrs: {},
1645
+ glyph: E.importHtml,
1646
+ glyphIsSvg: !0,
1647
+ divider: !0
1648
+ },
1649
+ {
1650
+ label: "Markdown",
1651
+ kind: "importMarkdown",
1652
+ attrs: {},
1653
+ glyph: E.importMarkdown,
1654
+ glyphIsSvg: !0
1655
+ }
1656
+ ];
1657
+ function Tt(n, e) {
1658
+ z();
1659
+ const t = document.createElement("div");
1660
+ t.className = "aw-popover aw-block-type-popover", t.dataset.popover = "true", S(t);
1661
+ for (const o of St) {
1662
+ if (o.divider) {
1663
+ const s = document.createElement("div");
1664
+ s.className = "aw-popover-divider", t.appendChild(s);
1665
+ }
1666
+ const l = document.createElement("button");
1667
+ l.type = "button", l.className = "aw-popover-item";
1668
+ const i = document.createElement("span");
1669
+ i.className = "aw-popover-item-glyph", o.glyphIsSvg ? i.innerHTML = o.glyph : i.textContent = o.glyph;
1670
+ const r = document.createElement("span");
1671
+ r.className = "aw-popover-item-label", r.textContent = o.label, o.size && (r.style.fontSize = o.size), o.weight && (r.style.fontWeight = o.weight), l.appendChild(i), l.appendChild(r), S(l), l.addEventListener("click", (s) => {
1672
+ s.stopPropagation(), t.remove(), e({ kind: o.kind, attrs: o.attrs });
1673
+ }), t.appendChild(l);
1674
+ }
1675
+ I(t, n);
1676
+ }
1677
+ function Nt(n, e) {
1678
+ z();
1679
+ const t = document.createElement("div");
1680
+ t.className = "aw-popover aw-align-popover", t.dataset.popover = "true", S(t);
1681
+ const o = [
1682
+ { icon: "alignLeft", value: "left", label: "왼쪽 정렬" },
1683
+ { icon: "alignCenter", value: "center", label: "가운데 정렬" },
1684
+ { icon: "alignRight", value: "right", label: "오른쪽 정렬" },
1685
+ { icon: "alignJustify", value: "justify", label: "양쪽 정렬" }
1686
+ ];
1687
+ for (const l of o) {
1688
+ const i = document.createElement("button");
1689
+ i.type = "button", i.className = "aw-popover-item aw-popover-item-icon", i.setAttribute("aria-label", l.label), i.title = l.label, i.innerHTML = E[l.icon] ?? "", S(i), i.addEventListener("click", (r) => {
1690
+ r.stopPropagation(), t.remove(), e(l.value);
1691
+ }), t.appendChild(i);
1692
+ }
1693
+ I(t, n);
1694
+ }
1695
+ function Mt(n, e, t) {
1696
+ lt(), z();
1697
+ const o = document.createElement("div");
1698
+ o.className = "aw-popover aw-font-family-popover", o.dataset.popover = "true", S(o);
1699
+ for (const l of W) {
1700
+ const i = document.createElement("button");
1701
+ i.type = "button", i.className = "aw-popover-item aw-font-family-item", t && t === l.id && i.classList.add("aw-popover-item-active"), i.title = l.label;
1702
+ const r = document.createElement("span");
1703
+ r.className = "aw-font-family-name", r.textContent = l.label;
1704
+ const s = document.createElement("span");
1705
+ s.className = "aw-font-family-preview", s.textContent = "가A", s.style.fontFamily = l.stack, i.appendChild(r), i.appendChild(s), S(i), i.addEventListener("click", (c) => {
1706
+ c.stopPropagation(), o.remove(), e({ id: l.id, label: l.label, stack: l.stack });
1707
+ }), o.appendChild(i);
1708
+ }
1709
+ I(o, n);
1710
+ }
1711
+ function Bt(n, e, t) {
1712
+ z();
1713
+ const o = document.createElement("div");
1714
+ o.className = "aw-popover aw-code-lang-popover", o.dataset.popover = "true", S(o);
1715
+ for (const l of Me) {
1716
+ const i = document.createElement("button");
1717
+ i.type = "button", i.className = "aw-popover-item aw-code-lang-item", t === l.id && i.classList.add("aw-popover-item-active"), i.title = l.label;
1718
+ const r = document.createElement("span");
1719
+ r.className = "aw-popover-item-label", r.textContent = l.label, i.appendChild(r), S(i), i.addEventListener("click", (s) => {
1720
+ s.stopPropagation(), o.remove(), e({ id: l.id, label: l.label });
1721
+ }), o.appendChild(i);
1722
+ }
1723
+ I(o, n);
1724
+ }
1725
+ const $t = [
1726
+ { action: "rowAbove", label: "위에 행 추가" },
1727
+ { action: "rowBelow", label: "아래에 행 추가" },
1728
+ { action: "colLeft", label: "왼쪽에 열 추가", divider: !0 },
1729
+ { action: "colRight", label: "오른쪽에 열 추가" },
1730
+ { action: "deleteRow", label: "행 삭제", divider: !0 },
1731
+ { action: "deleteCol", label: "열 삭제" }
1732
+ ];
1733
+ function Rt(n, e) {
1734
+ z();
1735
+ const t = document.createElement("div");
1736
+ t.className = "aw-popover aw-table-menu-popover", t.dataset.popover = "true", S(t);
1737
+ for (const o of $t) {
1738
+ if (o.divider) {
1739
+ const i = document.createElement("div");
1740
+ i.className = "aw-popover-divider", t.appendChild(i);
1741
+ }
1742
+ const l = document.createElement("button");
1743
+ l.type = "button", l.className = "aw-popover-item aw-table-menu-item", l.textContent = o.label, S(l), l.addEventListener("click", (i) => {
1744
+ i.stopPropagation(), t.remove(), e(o.action);
1745
+ }), t.appendChild(l);
1746
+ }
1747
+ I(t, n);
1748
+ }
1749
+ function It(n, e, t) {
1750
+ z();
1751
+ const o = document.createElement("div");
1752
+ o.className = "aw-popover aw-font-size-popover", o.dataset.popover = "true", S(o);
1753
+ for (const l of nt) {
1754
+ const i = document.createElement("button");
1755
+ i.type = "button", i.className = "aw-popover-item aw-font-size-item", t === l && i.classList.add("aw-popover-item-active"), i.title = `${l}px`;
1756
+ const r = document.createElement("span");
1757
+ r.className = "aw-font-size-label", r.textContent = String(l), i.appendChild(r), S(i), i.addEventListener("click", (s) => {
1758
+ s.stopPropagation(), o.remove(), e({ px: l });
1759
+ }), o.appendChild(i);
1760
+ }
1761
+ I(o, n);
1762
+ }
1763
+ function zt(n, e) {
1764
+ z();
1765
+ const t = 2, o = 12, l = document.createElement("div");
1766
+ l.className = "aw-popover aw-table-popover", l.dataset.popover = "true";
1767
+ const i = document.createElement("div");
1768
+ i.className = "aw-table-picker-grid";
1769
+ const r = document.createElement("div");
1770
+ r.className = "aw-table-status";
1771
+ let s = t, c = t, a = 0, d = 0;
1772
+ const u = [];
1773
+ function m() {
1774
+ for (let h = 0; h < u.length; h++) {
1775
+ const b = u[h];
1776
+ if (b)
1777
+ for (let v = 0; v < b.length; v++) {
1778
+ const k = b[v];
1779
+ k && k.classList.toggle("aw-table-cell-active", h < a && v < d);
1780
+ }
1781
+ }
1782
+ r.textContent = a > 0 && d > 0 ? `${a} × ${d} 표` : "크기를 선택하세요";
1783
+ }
1784
+ function f() {
1785
+ i.innerHTML = "", i.style.gridTemplateColumns = `repeat(${c}, 18px)`, i.style.gridTemplateRows = `repeat(${s}, 18px)`, u.length = 0;
1786
+ for (let h = 0; h < s; h++) {
1787
+ const b = [];
1788
+ for (let v = 0; v < c; v++) {
1789
+ const k = document.createElement("div");
1790
+ k.className = "aw-table-cell", k.dataset.row = String(h + 1), k.dataset.col = String(v + 1), i.appendChild(k), b.push(k);
1791
+ }
1792
+ u.push(b);
1793
+ }
1794
+ m();
1795
+ }
1796
+ f(), i.addEventListener("mousemove", (h) => {
1797
+ const b = h.target;
1798
+ if (!b.classList.contains("aw-table-cell")) return;
1799
+ const v = parseInt(b.dataset.row ?? "0", 10), k = parseInt(b.dataset.col ?? "0", 10);
1800
+ a = v, d = k;
1801
+ let T = !1;
1802
+ v >= s && s < o && (s = Math.min(o, v + 1), T = !0), k >= c && c < o && (c = Math.min(o, k + 1), T = !0), T ? f() : m();
1803
+ }), i.addEventListener("click", (h) => {
1804
+ const b = h.target;
1805
+ if (!b.classList.contains("aw-table-cell")) return;
1806
+ const v = parseInt(b.dataset.row ?? "0", 10), k = parseInt(b.dataset.col ?? "0", 10);
1807
+ v > 0 && k > 0 && (e({ rows: v, cols: k }), l.remove());
1808
+ });
1809
+ function p(h) {
1810
+ h.key === "Escape" && (l.remove(), document.removeEventListener("keydown", p));
1811
+ }
1812
+ document.addEventListener("keydown", p), l.appendChild(i), l.appendChild(r), I(l, n);
1813
+ }
1814
+ function I(n, e) {
1815
+ const t = e.closest(".aeriz-wysiwyg"), o = t == null ? void 0 : t.dataset.theme;
1816
+ o && (n.dataset.theme = o), n.style.position = "fixed";
1817
+ const l = () => {
1818
+ const a = e.getBoundingClientRect();
1819
+ n.style.top = `${a.bottom + 4}px`, n.style.left = `${a.left}px`;
1820
+ };
1821
+ l(), document.body.appendChild(n);
1822
+ const i = () => l(), r = () => l();
1823
+ window.addEventListener("scroll", i, !0), window.addEventListener("resize", r);
1824
+ function s(a) {
1825
+ !n.contains(a.target) && !e.contains(a.target) && n.remove();
1826
+ }
1827
+ setTimeout(() => {
1828
+ document.addEventListener("click", s);
1829
+ }, 0);
1830
+ const c = n.remove.bind(n);
1831
+ n.remove = function() {
1832
+ window.removeEventListener("scroll", i, !0), window.removeEventListener("resize", r), document.removeEventListener("click", s), c();
1833
+ };
1834
+ }
1835
+ function z() {
1836
+ document.querySelectorAll('[data-popover="true"]').forEach((n) => n.remove());
1837
+ }
1838
+ const Ht = ["script", "noscript", "meta", "link", "style", "base"];
1839
+ async function Dt(n, e = {}) {
1840
+ const t = e.settleMs ?? 1e3, o = e.timeoutMs ?? 3e4, l = e.width ?? 1024;
1841
+ return new Promise((i, r) => {
1842
+ const s = document.createElement("iframe");
1843
+ s.setAttribute("aria-hidden", "true"), s.setAttribute("tabindex", "-1"), s.style.position = "fixed", s.style.left = "-99999px", s.style.top = "0", s.style.width = `${l}px`, s.style.height = "768px", s.style.border = "0", s.style.opacity = "0", s.style.pointerEvents = "none";
1844
+ let c = !1;
1845
+ const a = () => {
1846
+ s.parentNode && s.parentNode.removeChild(s);
1847
+ }, d = (p) => {
1848
+ c || (c = !0, a(), r(p));
1849
+ }, u = (p) => {
1850
+ c || (c = !0, a(), i(p));
1851
+ }, m = window.setTimeout(() => {
1852
+ d(new Error("HTML 로드 시간 초과"));
1853
+ }, o), f = () => {
1854
+ var k;
1855
+ const p = s.contentDocument;
1856
+ if (!p || !p.body || p.body.children.length === 0 && (p.body.textContent ?? "").trim() === "")
1857
+ return;
1858
+ const h = s.contentWindow, b = (h == null ? void 0 : h.requestAnimationFrame) ?? window.requestAnimationFrame;
1859
+ (p.fonts && typeof ((k = p.fonts.ready) == null ? void 0 : k.then) == "function" ? p.fonts.ready : Promise.resolve()).then(() => {
1860
+ const T = () => {
1861
+ window.setTimeout(() => {
1862
+ if (!c)
1863
+ try {
1864
+ const y = s.contentWindow, L = s.contentDocument;
1865
+ if (!y || !L || !L.body) {
1866
+ window.clearTimeout(m), d(new Error("iframe document 접근 실패"));
1867
+ return;
1868
+ }
1869
+ const A = Ft(L.body, y);
1870
+ window.clearTimeout(m), u(A);
1871
+ } catch (y) {
1872
+ window.clearTimeout(m), d(y instanceof Error ? y : new Error(String(y)));
1873
+ }
1874
+ }, t);
1875
+ };
1876
+ b(() => b(T));
1877
+ });
1878
+ };
1879
+ s.addEventListener("load", f), s.srcdoc = n, document.body.appendChild(s);
1880
+ });
1881
+ }
1882
+ function Ft(n, e) {
1883
+ var l;
1884
+ const t = document.createElement("div");
1885
+ for (let i = 0; i < n.attributes.length; i++) {
1886
+ const r = n.attributes.item(i);
1887
+ r && r.name !== "style" && t.setAttribute(r.name, r.value);
1888
+ }
1889
+ const o = Be(n, e);
1890
+ o.length > 0 && t.setAttribute("style", o);
1891
+ for (const i of Array.from(n.children)) {
1892
+ const r = document.importNode(i, !0);
1893
+ $e(i, r, e), t.appendChild(r);
1894
+ }
1895
+ for (const i of Ht)
1896
+ for (const r of Array.from(t.querySelectorAll(i)))
1897
+ (l = r.parentNode) == null || l.removeChild(r);
1898
+ return t.style.width = "auto", t.style.maxWidth = "none", t.style.height = "auto", t.style.maxHeight = "none", t.setAttribute("data-imported-html", "true"), [t];
1899
+ }
1900
+ function Be(n, e) {
1901
+ const t = e.getComputedStyle(n);
1902
+ let o = "";
1903
+ for (let l = 0; l < t.length; l++) {
1904
+ const i = t.item(l);
1905
+ if (!i) continue;
1906
+ const r = t.getPropertyValue(i);
1907
+ r && r.length > 0 && (o += `${i}:${r};`);
1908
+ }
1909
+ return o;
1910
+ }
1911
+ function $e(n, e, t) {
1912
+ if (n.nodeType === 1 && e.nodeType === 1) {
1913
+ const r = Be(n, t);
1914
+ r.length > 0 && e.setAttribute("style", r);
1915
+ }
1916
+ const o = n.children, l = e.children, i = Math.min(o.length, l.length);
1917
+ for (let r = 0; r < i; r++) {
1918
+ const s = o[r], c = l[r];
1919
+ s && c && $e(s, c, t);
1920
+ }
1921
+ }
1922
+ function Pt(n) {
1923
+ const e = qt(n), t = document.createElement("div");
1924
+ t.innerHTML = e;
1925
+ const o = [];
1926
+ for (const l of Array.from(t.children))
1927
+ l instanceof HTMLElement && o.push(l);
1928
+ return o;
1929
+ }
1930
+ function qt(n) {
1931
+ const t = n.replace(/\r\n?/g, `
1932
+ `).replace(/\s+$/, "").split(`
1933
+ `), o = [];
1934
+ let l = 0;
1935
+ for (; l < t.length; ) {
1936
+ const i = t[l] ?? "";
1937
+ if (i.trim() === "") {
1938
+ l++;
1939
+ continue;
1940
+ }
1941
+ const r = /^```\s*([\w-]*)\s*$/.exec(i);
1942
+ if (r) {
1943
+ const a = r[1] ?? "", d = [];
1944
+ for (l++; l < t.length && !/^```\s*$/.test(t[l] ?? ""); )
1945
+ d.push(t[l] ?? ""), l++;
1946
+ l < t.length && l++;
1947
+ const u = a ? ` data-language="${j(a)}"` : "";
1948
+ o.push(
1949
+ `<pre><code${u}>${X(d.join(`
1950
+ `))}</code></pre>`
1951
+ );
1952
+ continue;
1953
+ }
1954
+ const s = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(i);
1955
+ if (s) {
1956
+ const a = (s[1] ?? "#").length;
1957
+ o.push(`<h${a}>${D(s[2] ?? "")}</h${a}>`), l++;
1958
+ continue;
1959
+ }
1960
+ if (/^[\s]*([-*_])(\s*\1){2,}[\s]*$/.test(i)) {
1961
+ o.push("<hr>"), l++;
1962
+ continue;
1963
+ }
1964
+ if (Re(i, t[l + 1] ?? "")) {
1965
+ const a = [i];
1966
+ for (l++, a.push(t[l] ?? ""), l++; l < t.length && (t[l] ?? "").includes("|") && (t[l] ?? "").trim() !== ""; )
1967
+ a.push(t[l] ?? ""), l++;
1968
+ o.push(_t(a));
1969
+ continue;
1970
+ }
1971
+ if (i.startsWith(">")) {
1972
+ const a = [];
1973
+ for (; l < t.length && (t[l] ?? "").startsWith(">"); )
1974
+ a.push((t[l] ?? "").replace(/^>\s?/, "")), l++;
1975
+ const d = D(a.join(" ").trim());
1976
+ o.push(`<blockquote><p>${d}</p></blockquote>`);
1977
+ continue;
1978
+ }
1979
+ if (/^[\s]*[-*+]\s+/.test(i)) {
1980
+ const a = ye(t, l, /^[\s]*[-*+]\s+(.*)$/);
1981
+ l = a.next, o.push(`<ul>${a.html}</ul>`);
1982
+ continue;
1983
+ }
1984
+ if (/^[\s]*\d+\.\s+/.test(i)) {
1985
+ const a = ye(t, l, /^[\s]*\d+\.\s+(.*)$/);
1986
+ l = a.next, o.push(`<ol>${a.html}</ol>`);
1987
+ continue;
1988
+ }
1989
+ const c = [];
1990
+ for (; l < t.length && (t[l] ?? "").trim() !== "" && !Ot(t[l] ?? "", t[l + 1] ?? ""); )
1991
+ c.push(t[l] ?? ""), l++;
1992
+ c.length > 0 && o.push(`<p>${D(jt(c))}</p>`);
1993
+ }
1994
+ return o.join(`
1995
+ `);
1996
+ }
1997
+ function Ot(n, e) {
1998
+ return /^#{1,6}\s/.test(n) || /^[\s]*[-*+]\s+/.test(n) || /^[\s]*\d+\.\s+/.test(n) || n.startsWith(">") || /^```/.test(n) || /^[\s]*([-*_])(\s*\1){2,}[\s]*$/.test(n) || Re(n, e);
1999
+ }
2000
+ function Re(n, e) {
2001
+ return n.includes("|") ? /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/.test(e) : !1;
2002
+ }
2003
+ function _t(n) {
2004
+ if (n.length < 2) return "";
2005
+ const e = ne(n[0] ?? ""), o = ne(n[1] ?? "").map((s) => {
2006
+ const c = s.trim(), a = c.startsWith(":"), d = c.endsWith(":");
2007
+ return a && d ? "center" : d ? "right" : a ? "left" : null;
2008
+ }), l = n.slice(2).map(ne), i = e.map((s, c) => {
2009
+ const a = o[c];
2010
+ return `<th${a ? ` style="text-align:${a}"` : ""}>${D(s.trim())}</th>`;
2011
+ }).join(""), r = l.map((s) => `<tr>${s.map((a, d) => {
2012
+ const u = o[d];
2013
+ return `<td${u ? ` style="text-align:${u}"` : ""}>${D(a.trim())}</td>`;
2014
+ }).join("")}</tr>`).join("");
2015
+ return `<table><thead><tr>${i}</tr></thead><tbody>${r}</tbody></table>`;
2016
+ }
2017
+ function ne(n) {
2018
+ return n.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
2019
+ }
2020
+ function ye(n, e, t) {
2021
+ const o = [];
2022
+ let l = e;
2023
+ for (; l < n.length; ) {
2024
+ const i = n[l] ?? "";
2025
+ if (i.trim() === "") {
2026
+ const s = n[l + 1] ?? "";
2027
+ if (t.test(s)) {
2028
+ l++;
2029
+ continue;
2030
+ }
2031
+ break;
2032
+ }
2033
+ const r = t.exec(i);
2034
+ if (!r) break;
2035
+ o.push(`<li>${D(r[1] ?? "")}</li>`), l++;
2036
+ }
2037
+ return { html: o.join(""), next: l };
2038
+ }
2039
+ function jt(n) {
2040
+ const e = [];
2041
+ for (let t = 0; t < n.length; t++) {
2042
+ const o = n[t] ?? "", l = t === n.length - 1;
2043
+ !l && / +$/.test(o) ? e.push(o.replace(/\s+$/, "") + `
2044
+ <br>
2045
+ `) : e.push(l ? o : o + " ");
2046
+ }
2047
+ return e.join("");
2048
+ }
2049
+ function D(n) {
2050
+ const e = [];
2051
+ let t = n.replace(/(`+)([\s\S]+?)\1/g, (o, l, i) => `\0CODE${e.push(`<code>${X(i)}</code>`) - 1}\0`);
2052
+ return t = X(t), t = t.replace(
2053
+ /&lt;(https?:\/\/[^\s&]+)&gt;/g,
2054
+ (o, l) => `<a href="${j(l)}" target="_blank" rel="noopener">${X(l)}</a>`
2055
+ ), t = t.replace(
2056
+ /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g,
2057
+ (o, l, i) => `<img src="${j(i)}" alt="${j(l)}">`
2058
+ ), t = t.replace(
2059
+ /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g,
2060
+ (o, l, i) => `<a href="${j(i)}" target="_blank" rel="noopener">${l}</a>`
2061
+ ), t = t.replace(/\*\*([^*\n]+?)\*\*/g, "<strong>$1</strong>"), t = t.replace(/__([^_\n]+?)__/g, "<strong>$1</strong>"), t = t.replace(/(^|[^*])\*([^*\n]+?)\*(?!\*)/g, "$1<em>$2</em>"), t = t.replace(/(^|[^_])_([^_\n]+?)_(?!_)/g, "$1<em>$2</em>"), t = t.replace(/~~([^~\n]+?)~~/g, "<s>$1</s>"), t = t.replace(/CODE(\d+)/g, (o, l) => {
2062
+ const i = Number(l);
2063
+ return e[i] ?? "";
2064
+ }), t;
2065
+ }
2066
+ function X(n) {
2067
+ return n.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2068
+ }
2069
+ function j(n) {
2070
+ return n.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
2071
+ }
2072
+ const Q = [
2073
+ "bold",
2074
+ "italic",
2075
+ "underline",
2076
+ "strike"
2077
+ ], Ut = {
2078
+ kind: "paragraph",
2079
+ label: "단락",
2080
+ allowsInlineFormat: !0,
2081
+ allowedMarks: Q,
2082
+ matches: (n) => n.tagName === "P" ? { kind: "paragraph", attrs: {} } : null,
2083
+ create: () => J(document.createElement("p")),
2084
+ isEmpty: (n) => q(n)
2085
+ }, Wt = {
2086
+ kind: "heading",
2087
+ label: "제목",
2088
+ allowsInlineFormat: !0,
2089
+ // 사용자 지정 — 제목은 italic 만 허용. (bold 는 heading 자체가 굵으므로
2090
+ // 중복, underline / strike 는 가독성/시맨틱 면에서 부적합.)
2091
+ allowedMarks: ["italic"],
2092
+ matches: (n) => {
2093
+ const e = /^H([1-6])$/.exec(n.tagName);
2094
+ return e ? { kind: "heading", attrs: { level: Number(e[1]) } } : null;
2095
+ },
2096
+ create: (n) => {
2097
+ const e = wn(n == null ? void 0 : n.level);
2098
+ return J(document.createElement(`h${e}`));
2099
+ },
2100
+ isEmpty: (n) => q(n)
2101
+ }, Kt = {
2102
+ kind: "bulletList",
2103
+ label: "글머리 목록",
2104
+ allowsInlineFormat: !0,
2105
+ allowedMarks: Q,
2106
+ matches: (n) => n.tagName === "UL" ? { kind: "bulletList", attrs: {} } : null,
2107
+ create: () => _e("ul"),
2108
+ isEmpty: (n) => je(n)
2109
+ }, Yt = {
2110
+ kind: "orderedList",
2111
+ label: "번호 매기기 목록",
2112
+ allowsInlineFormat: !0,
2113
+ allowedMarks: Q,
2114
+ matches: (n) => n.tagName === "OL" ? { kind: "orderedList", attrs: {} } : null,
2115
+ create: () => _e("ol"),
2116
+ isEmpty: (n) => je(n)
2117
+ }, Vt = {
2118
+ kind: "blockquote",
2119
+ label: "인용구",
2120
+ allowsInlineFormat: !0,
2121
+ // 사용자 지정 — 인용구는 italic 제외(인용 자체가 이미 italic 으로 렌더되어
2122
+ // 중복). bold / underline / strike 만 허용.
2123
+ allowedMarks: ["bold", "underline", "strike"],
2124
+ matches: (n) => n.tagName === "BLOCKQUOTE" ? { kind: "blockquote", attrs: {} } : null,
2125
+ create: () => {
2126
+ const n = document.createElement("blockquote");
2127
+ return n.appendChild(J(document.createElement("p"))), n;
2128
+ },
2129
+ isEmpty: (n) => q(n)
2130
+ }, Gt = {
2131
+ kind: "codeBlock",
2132
+ label: "코드 블록",
2133
+ allowsInlineFormat: !1,
2134
+ allowedMarks: [],
2135
+ matches: (n) => {
2136
+ if (n.tagName !== "PRE") return null;
2137
+ const e = n.querySelector(":scope > code"), t = e == null ? void 0 : e.getAttribute("data-language");
2138
+ return { kind: "codeBlock", attrs: t ? { language: t } : {} };
2139
+ },
2140
+ create: (n) => {
2141
+ const e = document.createElement("pre"), t = document.createElement("code");
2142
+ return n != null && n.language && t.setAttribute("data-language", n.language), e.appendChild(t), e;
2143
+ },
2144
+ isEmpty: (n) => q(n)
2145
+ }, Zt = {
2146
+ kind: "image",
2147
+ label: "이미지",
2148
+ allowsInlineFormat: !1,
2149
+ allowedMarks: [],
2150
+ matches: (n) => {
2151
+ if (n.tagName === "FIGURE") {
2152
+ const e = n.querySelector(":scope > img"), t = Ce(e), o = n.getAttribute("style");
2153
+ return o && (t.wrapperStyle = o), { kind: "image", attrs: t };
2154
+ }
2155
+ return n.tagName === "IMG" ? { kind: "image", attrs: Ce(n) } : null;
2156
+ },
2157
+ create: (n) => {
2158
+ const e = document.createElement("figure");
2159
+ n != null && n.wrapperStyle && e.setAttribute("style", n.wrapperStyle);
2160
+ const t = document.createElement("img");
2161
+ return n != null && n.src && (t.src = n.src), n != null && n.alt && (t.alt = n.alt), e.appendChild(t), e.appendChild(he()), e.appendChild(pe()), e;
2162
+ },
2163
+ isEmpty: (n) => {
2164
+ const e = n.tagName === "IMG" ? n : n.querySelector(":scope > img");
2165
+ return !e || !e.getAttribute("src");
2166
+ }
2167
+ };
2168
+ function pe() {
2169
+ const n = document.createElement("span");
2170
+ return n.className = "aw-resize-handle", n.setAttribute("contenteditable", "false"), n.setAttribute("aria-hidden", "true"), n;
2171
+ }
2172
+ function he() {
2173
+ const n = document.createElement("span");
2174
+ n.className = "aw-block-align-controls", n.setAttribute("contenteditable", "false"), n.setAttribute("aria-hidden", "true");
2175
+ for (const e of ["left", "center", "right"]) {
2176
+ const t = document.createElement("button");
2177
+ t.type = "button", t.className = "aw-block-align-btn", t.dataset.align = e, t.title = e === "left" ? "왼쪽 정렬" : e === "center" ? "가운데 정렬" : "오른쪽 정렬", t.setAttribute("aria-label", t.title), t.innerHTML = Xt[e], n.appendChild(t);
2178
+ }
2179
+ return n;
2180
+ }
2181
+ const Xt = {
2182
+ left: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg>',
2183
+ center: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="10" x2="6" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="18" y1="18" x2="6" y2="18"/></svg>',
2184
+ right: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="10" x2="7" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="7" y2="18"/></svg>'
2185
+ }, Qt = {
2186
+ kind: "table",
2187
+ label: "표",
2188
+ allowsInlineFormat: !0,
2189
+ allowedMarks: Q,
2190
+ matches: (n) => {
2191
+ var l;
2192
+ if (n.tagName !== "TABLE") return null;
2193
+ const e = n.querySelectorAll(":scope > tbody > tr, :scope > tr"), t = e.length, o = ((l = e[0]) == null ? void 0 : l.children.length) ?? 0;
2194
+ return { kind: "table", attrs: { rows: t, cols: o } };
2195
+ },
2196
+ create: (n) => {
2197
+ const e = Math.max(1, (n == null ? void 0 : n.rows) ?? 1), t = Math.max(1, (n == null ? void 0 : n.cols) ?? 1), o = document.createElement("table"), l = document.createElement("tbody");
2198
+ for (let i = 0; i < e; i++) {
2199
+ const r = document.createElement("tr");
2200
+ for (let s = 0; s < t; s++) {
2201
+ const c = document.createElement("td");
2202
+ c.appendChild(document.createElement("br")), r.appendChild(c);
2203
+ }
2204
+ l.appendChild(r);
2205
+ }
2206
+ return o.appendChild(l), o;
2207
+ },
2208
+ isEmpty: (n) => n.querySelectorAll(":scope tr").length === 0
2209
+ }, Jt = {
2210
+ kind: "hr",
2211
+ label: "구분선",
2212
+ allowsInlineFormat: !1,
2213
+ allowedMarks: [],
2214
+ matches: (n) => n.tagName === "HR" ? { kind: "hr", attrs: {} } : null,
2215
+ create: () => {
2216
+ const n = document.createElement("hr");
2217
+ return n.setAttribute("contenteditable", "false"), n;
2218
+ },
2219
+ isEmpty: () => !1
2220
+ }, en = {
2221
+ kind: "youtube",
2222
+ label: "유튜브",
2223
+ allowsInlineFormat: !1,
2224
+ allowedMarks: [],
2225
+ matches: (n) => {
2226
+ if (n.tagName !== "DIV" || !n.classList.contains("aw-youtube-embed")) return null;
2227
+ const e = n.getAttribute("data-youtube-id") ?? void 0, t = {};
2228
+ e && (t.videoId = e);
2229
+ const o = n.getAttribute("style");
2230
+ return o && (t.wrapperStyle = o), { kind: "youtube", attrs: t };
2231
+ },
2232
+ create: (n) => {
2233
+ const e = (n == null ? void 0 : n.videoId) ?? "", t = document.createElement("div");
2234
+ t.className = "aw-youtube-embed", t.setAttribute("contenteditable", "false"), e && t.setAttribute("data-youtube-id", e), n != null && n.wrapperStyle && t.setAttribute("style", n.wrapperStyle);
2235
+ const o = document.createElement("iframe");
2236
+ return e && (o.src = `https://www.youtube-nocookie.com/embed/${e}`), o.setAttribute(
2237
+ "allow",
2238
+ "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
2239
+ ), o.setAttribute("allowfullscreen", ""), o.setAttribute("frameborder", "0"), o.setAttribute("referrerpolicy", "strict-origin-when-cross-origin"), t.appendChild(o), t.appendChild(he()), t.appendChild(pe()), t;
2240
+ },
2241
+ isEmpty: (n) => !n.getAttribute("data-youtube-id")
2242
+ }, tn = {
2243
+ kind: "linkCard",
2244
+ label: "링크",
2245
+ allowsInlineFormat: !1,
2246
+ allowedMarks: [],
2247
+ matches: (n) => {
2248
+ var s, c, a, d, u;
2249
+ if (n.tagName !== "A" || !n.classList.contains("aw-link-card")) return null;
2250
+ const e = n.getAttribute("href") ?? void 0, t = ((s = n.querySelector(":scope > .aw-link-card-body > .aw-link-card-title")) == null ? void 0 : s.textContent) ?? ((c = n.querySelector(":scope > .aw-link-card-text")) == null ? void 0 : c.textContent) ?? void 0, o = ((a = n.querySelector(":scope > .aw-link-card-body > .aw-link-card-desc")) == null ? void 0 : a.textContent) ?? void 0, l = ((d = n.querySelector(":scope > .aw-link-card-image")) == null ? void 0 : d.getAttribute("src")) ?? void 0, i = ((u = n.querySelector(":scope .aw-link-card-favicon")) == null ? void 0 : u.getAttribute("src")) ?? void 0, r = {};
2251
+ return e && (r.href = e), t && (r.text = t), o && (r.description = o), l && (r.image = l), i && (r.favicon = i), { kind: "linkCard", attrs: r };
2252
+ },
2253
+ create: (n) => {
2254
+ const e = (n == null ? void 0 : n.href) ?? "#", t = n != null && n.text && n.text.length > 0 ? n.text : Ie(e) || e, o = (n == null ? void 0 : n.description) ?? "", l = (n == null ? void 0 : n.image) ?? "", i = (n == null ? void 0 : n.favicon) ?? nn(e), r = document.createElement("a");
2255
+ if (r.className = "aw-link-card", l && r.classList.add("aw-link-card-with-image"), r.href = e, r.target = "_blank", r.rel = "noopener", r.setAttribute("contenteditable", "false"), l) {
2256
+ const u = document.createElement("img");
2257
+ u.className = "aw-link-card-image", u.src = l, u.alt = "", u.loading = "lazy", r.appendChild(u);
2258
+ }
2259
+ const s = document.createElement("span");
2260
+ s.className = "aw-link-card-body";
2261
+ const c = document.createElement("span");
2262
+ if (c.className = "aw-link-card-title", c.textContent = t, s.appendChild(c), o) {
2263
+ const u = document.createElement("span");
2264
+ u.className = "aw-link-card-desc", u.textContent = o, s.appendChild(u);
2265
+ }
2266
+ const a = document.createElement("span");
2267
+ if (a.className = "aw-link-card-url", i) {
2268
+ const u = document.createElement("img");
2269
+ u.className = "aw-link-card-favicon", u.src = i, u.alt = "", u.loading = "lazy", a.appendChild(u);
2270
+ }
2271
+ const d = document.createElement("span");
2272
+ return d.className = "aw-link-card-url-text", d.textContent = e, a.appendChild(d), s.appendChild(a), r.appendChild(s), r;
2273
+ },
2274
+ isEmpty: (n) => !n.getAttribute("href")
2275
+ };
2276
+ function Ie(n) {
2277
+ try {
2278
+ return new URL(n).hostname;
2279
+ } catch {
2280
+ return "";
2281
+ }
2282
+ }
2283
+ function nn(n) {
2284
+ const e = Ie(n);
2285
+ return e ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(e)}&sz=64` : "";
2286
+ }
2287
+ const on = {
2288
+ kind: "file",
2289
+ label: "파일",
2290
+ allowsInlineFormat: !1,
2291
+ allowedMarks: [],
2292
+ matches: (n) => {
2293
+ if (n.tagName !== "A" || !n.classList.contains("aw-file-attachment")) return null;
2294
+ const e = n.getAttribute("href") ?? void 0, t = n.getAttribute("data-file-name") ?? void 0, o = n.getAttribute("data-file-size"), l = o ? Number(o) : void 0, i = n.getAttribute("data-file-type") ?? void 0, r = {};
2295
+ return e && (r.fileSrc = e), t && (r.fileName = t), l !== void 0 && !Number.isNaN(l) && (r.fileSize = l), i && (r.fileType = i), { kind: "file", attrs: r };
2296
+ },
2297
+ create: (n) => {
2298
+ const e = document.createElement("a");
2299
+ e.className = "aw-file-attachment", e.setAttribute("contenteditable", "false"), e.setAttribute("target", "_blank"), e.setAttribute("rel", "noopener"), n != null && n.fileSrc && (e.href = n.fileSrc), e.setAttribute("download", (n == null ? void 0 : n.fileName) ?? ""), n != null && n.fileName && e.setAttribute("data-file-name", n.fileName), (n == null ? void 0 : n.fileSize) !== void 0 && e.setAttribute("data-file-size", String(n.fileSize)), n != null && n.fileType && e.setAttribute("data-file-type", n.fileType);
2300
+ const t = document.createElement("span");
2301
+ t.className = "aw-file-icon", t.setAttribute("aria-hidden", "true"), t.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6"/></svg>';
2302
+ const o = document.createElement("span");
2303
+ o.className = "aw-file-meta";
2304
+ const l = document.createElement("span");
2305
+ l.className = "aw-file-name", l.textContent = (n == null ? void 0 : n.fileName) ?? "파일", o.appendChild(l);
2306
+ const i = [], r = rn(n == null ? void 0 : n.fileName, n == null ? void 0 : n.fileType);
2307
+ if (r && i.push(r), (n == null ? void 0 : n.fileSize) !== void 0 && i.push(ln(n.fileSize)), i.length > 0) {
2308
+ const c = document.createElement("span");
2309
+ c.className = "aw-file-info", c.textContent = i.join(" · "), o.appendChild(c);
2310
+ }
2311
+ const s = document.createElement("span");
2312
+ return s.className = "aw-file-action", s.setAttribute("aria-hidden", "true"), s.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>', e.appendChild(t), e.appendChild(o), e.appendChild(s), e;
2313
+ },
2314
+ isEmpty: (n) => !n.getAttribute("href")
2315
+ };
2316
+ function ln(n) {
2317
+ return n < 1024 ? `${n} B` : n < 1024 * 1024 ? `${(n / 1024).toFixed(1)} KB` : n < 1024 * 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MB` : `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
2318
+ }
2319
+ function rn(n, e) {
2320
+ if (n) {
2321
+ const t = n.lastIndexOf(".");
2322
+ if (t > 0 && t < n.length - 1)
2323
+ return n.slice(t + 1).toUpperCase();
2324
+ }
2325
+ return e ? (e.split("/").pop() ?? "").toUpperCase() : "";
2326
+ }
2327
+ const sn = {
2328
+ kind: "importedHtml",
2329
+ label: "HTML",
2330
+ allowsInlineFormat: !1,
2331
+ allowedMarks: [],
2332
+ matches: (n) => {
2333
+ if (n.tagName !== "DIV" || n.getAttribute("data-imported-html") !== "true") return null;
2334
+ const e = {}, t = n.getAttribute("style");
2335
+ t && (e.importedStyle = t);
2336
+ const o = n.getAttribute("class");
2337
+ return o && (e.importedClass = o), { kind: "importedHtml", attrs: e };
2338
+ },
2339
+ create: (n) => {
2340
+ const e = document.createElement("div");
2341
+ return e.setAttribute("data-imported-html", "true"), n != null && n.importedStyle && e.setAttribute("style", n.importedStyle), n != null && n.importedClass && e.setAttribute("class", n.importedClass), e;
2342
+ },
2343
+ isEmpty: () => !1
2344
+ }, ze = {
2345
+ kind: "unknown",
2346
+ label: "알 수 없음",
2347
+ allowsInlineFormat: !1,
2348
+ allowedMarks: [],
2349
+ matches: () => null,
2350
+ create: () => J(document.createElement("p")),
2351
+ isEmpty: (n) => q(n)
2352
+ }, He = [
2353
+ Ut,
2354
+ Wt,
2355
+ Kt,
2356
+ Yt,
2357
+ Vt,
2358
+ Gt,
2359
+ Zt,
2360
+ Qt,
2361
+ Jt,
2362
+ tn,
2363
+ en,
2364
+ on,
2365
+ sn
2366
+ ], H = (() => {
2367
+ const n = { unknown: ze };
2368
+ for (const e of He) n[e.kind] = e;
2369
+ return n;
2370
+ })();
2371
+ function U(n) {
2372
+ for (const e of He) {
2373
+ const t = e.matches(n);
2374
+ if (t) return { type: e, match: t };
2375
+ }
2376
+ return { type: ze, match: { kind: "unknown", attrs: {} } };
2377
+ }
2378
+ const F = "data-block-id";
2379
+ class De {
2380
+ constructor() {
2381
+ w(this, "counter", 0);
2382
+ w(this, "seen", /* @__PURE__ */ new Set());
2383
+ }
2384
+ /** 외부에서 관측된 id (HTML 에 이미 있던 값 등) 를 이후 발급 시 회피하도록 등록. */
2385
+ observe(e) {
2386
+ this.seen.add(e);
2387
+ }
2388
+ /** seen 과 충돌하지 않는 새 id 를 발급해 반환. */
2389
+ generate() {
2390
+ let e;
2391
+ do {
2392
+ const t = (++this.counter).toString(36), o = Math.random().toString(36).slice(2, 6);
2393
+ e = `b${t}_${o}`;
2394
+ } while (this.seen.has(e));
2395
+ return this.seen.add(e), e;
2396
+ }
2397
+ /**
2398
+ * 단일 element 발급기. data-block-id 가 있으면 등록 후 그대로 반환,
2399
+ * 없으면 신규 발급 후 element 에 기록한다. 같은 id 의 중복 element 처리는
2400
+ * 이 함수 단독으로는 못 하므로, 본문 전체 정규화는 {@link assignMissingIds}
2401
+ * 를 사용해야 한다.
2402
+ */
2403
+ ensure(e) {
2404
+ const t = e.getAttribute(F);
2405
+ if (t)
2406
+ return this.seen.add(t), t;
2407
+ const o = this.generate();
2408
+ return e.setAttribute(F, o), o;
2409
+ }
2410
+ get(e) {
2411
+ return e.getAttribute(F);
2412
+ }
2413
+ }
2414
+ function ce(n, e) {
2415
+ const t = /* @__PURE__ */ new Set();
2416
+ for (const o of Array.from(n.children)) {
2417
+ if (!(o instanceof HTMLElement)) continue;
2418
+ const l = o.getAttribute(F);
2419
+ if (l && !t.has(l)) {
2420
+ t.add(l), e.observe(l);
2421
+ continue;
2422
+ }
2423
+ const i = e.generate();
2424
+ o.setAttribute(F, i), t.add(i);
2425
+ }
2426
+ }
2427
+ function we(n) {
2428
+ for (const e of Array.from(n.childNodes)) {
2429
+ if (e.nodeType !== Node.TEXT_NODE) continue;
2430
+ if ((e.textContent ?? "").trim().length === 0) {
2431
+ e.remove();
2432
+ continue;
2433
+ }
2434
+ const o = document.createElement("p");
2435
+ n.replaceChild(o, e), o.appendChild(e);
2436
+ }
2437
+ for (const e of Array.from(n.querySelectorAll("hr")))
2438
+ e.getAttribute("contenteditable") !== "false" && e.setAttribute("contenteditable", "false");
2439
+ for (const e of Array.from(n.querySelectorAll("a.aw-link-card")))
2440
+ e.getAttribute("contenteditable") !== "false" && e.setAttribute("contenteditable", "false");
2441
+ for (const e of Array.from(n.querySelectorAll("div.aw-youtube-embed")))
2442
+ e.getAttribute("contenteditable") !== "false" && e.setAttribute("contenteditable", "false");
2443
+ for (const e of Array.from(n.querySelectorAll("a.aw-file-attachment")))
2444
+ e.getAttribute("contenteditable") !== "false" && e.setAttribute("contenteditable", "false");
2445
+ for (const e of Array.from(n.querySelectorAll("figure, div.aw-youtube-embed")))
2446
+ e instanceof HTMLElement && (e.querySelector(":scope > .aw-block-align-controls") || e.appendChild(he()), e.querySelector(":scope > .aw-resize-handle") || e.appendChild(pe()));
2447
+ }
2448
+ function an(n) {
2449
+ switch (n.kind) {
2450
+ case "paragraph":
2451
+ return "단락";
2452
+ case "heading":
2453
+ return `제목 ${n.attrs.level ?? 1}`;
2454
+ case "bulletList":
2455
+ return "글머리 목록";
2456
+ case "orderedList":
2457
+ return "번호 목록";
2458
+ case "blockquote":
2459
+ return "인용구";
2460
+ case "codeBlock":
2461
+ return "코드";
2462
+ case "image":
2463
+ return "이미지";
2464
+ case "table":
2465
+ return "표";
2466
+ case "hr":
2467
+ return "구분선";
2468
+ case "linkCard":
2469
+ return "링크";
2470
+ case "youtube":
2471
+ return "유튜브";
2472
+ case "file":
2473
+ return "파일";
2474
+ case "importedHtml":
2475
+ return "HTML";
2476
+ default:
2477
+ return "?";
2478
+ }
2479
+ }
2480
+ function cn(n) {
2481
+ return {
2482
+ id: n.id,
2483
+ kind: n.kind,
2484
+ attrs: { ...n.attrs },
2485
+ html: n.element.innerHTML
2486
+ };
2487
+ }
2488
+ function Fe(n) {
2489
+ return n.map(cn);
2490
+ }
2491
+ function dn(n) {
2492
+ const t = (H[n.kind] ?? H.unknown).create(n.attrs);
2493
+ return t.setAttribute(F, n.id), n.html.length > 0 && (t.innerHTML = n.html), t;
2494
+ }
2495
+ function un(n, e) {
2496
+ for (; n.firstChild; ) n.removeChild(n.firstChild);
2497
+ for (const t of e)
2498
+ n.appendChild(dn(t));
2499
+ }
2500
+ function de(n, e) {
2501
+ const t = [];
2502
+ for (const o of Array.from(n.children)) {
2503
+ if (!(o instanceof HTMLElement)) continue;
2504
+ const { match: l } = U(o);
2505
+ t.push({
2506
+ id: e(o),
2507
+ kind: l.kind,
2508
+ attrs: l.attrs,
2509
+ element: o
2510
+ });
2511
+ }
2512
+ return t;
2513
+ }
2514
+ function ue(n, e, t) {
2515
+ let o = n;
2516
+ for (; o && o !== e; ) {
2517
+ if (o.parentNode === e && o.nodeType === Node.ELEMENT_NODE) {
2518
+ const l = o, { match: i } = U(l);
2519
+ return {
2520
+ id: t(l),
2521
+ kind: i.kind,
2522
+ attrs: i.attrs,
2523
+ element: l
2524
+ };
2525
+ }
2526
+ o = o.parentNode;
2527
+ }
2528
+ return null;
2529
+ }
2530
+ function pn(n) {
2531
+ const e = n.element;
2532
+ switch (n.kind) {
2533
+ case "paragraph":
2534
+ case "heading":
2535
+ return Array.from(e.childNodes);
2536
+ case "bulletList":
2537
+ case "orderedList": {
2538
+ const t = Array.from(e.querySelectorAll(":scope > li"));
2539
+ return ke(t);
2540
+ }
2541
+ case "blockquote": {
2542
+ const t = Array.from(e.querySelectorAll(":scope > p"));
2543
+ return t.length > 0 ? ke(t) : Array.from(e.childNodes);
2544
+ }
2545
+ case "codeBlock": {
2546
+ const t = e.querySelector(":scope > code");
2547
+ return t ? Array.from(t.childNodes) : Array.from(e.childNodes);
2548
+ }
2549
+ default:
2550
+ return [];
2551
+ }
2552
+ }
2553
+ function ke(n) {
2554
+ const e = [];
2555
+ return n.forEach((t, o) => {
2556
+ o > 0 && e.push(document.createElement("br"));
2557
+ for (const l of Array.from(t.childNodes)) e.push(l);
2558
+ }), e;
2559
+ }
2560
+ function hn(n, e, t) {
2561
+ if (t.length === 0) return;
2562
+ let o = null;
2563
+ switch (e) {
2564
+ case "paragraph":
2565
+ case "heading":
2566
+ o = n;
2567
+ break;
2568
+ case "bulletList":
2569
+ case "orderedList":
2570
+ o = n.querySelector(":scope > li");
2571
+ break;
2572
+ case "blockquote":
2573
+ o = n.querySelector(":scope > p");
2574
+ break;
2575
+ case "codeBlock":
2576
+ o = n.querySelector(":scope > code");
2577
+ break;
2578
+ default:
2579
+ return;
2580
+ }
2581
+ if (o) {
2582
+ for (; o.firstChild; ) o.removeChild(o.firstChild);
2583
+ for (const l of t) o.appendChild(l);
2584
+ }
2585
+ }
2586
+ function mn(n = {}) {
2587
+ const e = n.fg || "currentColor", t = n.bg || "transparent", o = n.mode === "dark" ? !0 : n.mode === "light" ? !1 : fn(e, t);
2588
+ return {
2589
+ primary: n.primary || "#64c8ff",
2590
+ fg: e,
2591
+ bg: t,
2592
+ border: n.border || "rgba(127,127,127,0.25)",
2593
+ fgMuted: n.fgMuted || "rgba(127,127,127,0.6)",
2594
+ isDark: o
2595
+ };
2596
+ }
2597
+ function fn(n, e) {
2598
+ const t = ve(n);
2599
+ if (t !== null) return t > 0.5;
2600
+ const o = ve(e);
2601
+ return o !== null ? o < 0.5 : !0;
2602
+ }
2603
+ function ve(n) {
2604
+ if (!n) return null;
2605
+ const e = n.trim();
2606
+ let t = -1, o = -1, l = -1;
2607
+ const i = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(e);
2608
+ if (i && i[1]) {
2609
+ const r = i[1];
2610
+ r.length === 3 ? (t = parseInt(r[0] + r[0], 16), o = parseInt(r[1] + r[1], 16), l = parseInt(r[2] + r[2], 16)) : (t = parseInt(r.slice(0, 2), 16), o = parseInt(r.slice(2, 4), 16), l = parseInt(r.slice(4, 6), 16));
2611
+ } else {
2612
+ const r = /rgba?\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)/.exec(e);
2613
+ r && (t = Number(r[1]), o = Number(r[2]), l = Number(r[3]));
2614
+ }
2615
+ return t < 0 ? null : (0.299 * t + 0.587 * o + 0.114 * l) / 255;
2616
+ }
2617
+ function Pe(n) {
2618
+ const e = getComputedStyle(n), t = (a) => {
2619
+ const d = e.getPropertyValue(a).trim();
2620
+ return d.length > 0 ? d : void 0;
2621
+ }, o = {}, l = t("--aw-primary"), i = t("--aw-fg"), r = t("--aw-bg"), s = t("--aw-border"), c = t("--aw-fg-muted");
2622
+ return l && (o.primary = l), i && (o.fg = i), r && (o.bg = r), s && (o.border = s), c && (o.fgMuted = c), o;
2623
+ }
2624
+ function gn(n) {
2625
+ const e = `color-mix(in srgb, ${n.primary} 8%, transparent)`, t = `color-mix(in srgb, ${n.fg} 6%, ${n.bg})`;
2626
+ return `${n.fg}${n.bg}`, {
2627
+ H1: `font-size:1.875rem;font-weight:700;line-height:1.3;margin:.6em 0 .4em;padding-bottom:.25em;border-bottom:1px solid ${n.border};color:${n.fg};`,
2628
+ H2: `font-size:1.5rem;font-weight:700;line-height:1.35;margin:.6em 0 .4em;padding-bottom:.25em;border-bottom:1px solid ${n.border};color:${n.fg};`,
2629
+ H3: `font-size:1.25rem;font-weight:700;line-height:1.4;margin:.6em 0 .4em;padding-bottom:.25em;border-bottom:1px solid ${n.border};color:${n.fg};`,
2630
+ H4: `font-size:1.125rem;font-weight:700;line-height:1.4;margin:.6em 0 .4em;color:${n.fg};`,
2631
+ H5: `font-size:1rem;font-weight:700;margin:.6em 0 .4em;color:${n.fg};`,
2632
+ H6: `font-size:.875rem;font-weight:700;margin:.6em 0 .4em;color:${n.fg};`,
2633
+ P: `margin:0 0 .8em;color:${n.fg};`,
2634
+ UL: `margin:.6em 0;padding-left:1.6em;list-style:disc;color:${n.fg};`,
2635
+ OL: `margin:.6em 0;padding-left:1.6em;list-style:decimal;color:${n.fg};`,
2636
+ BLOCKQUOTE: `margin:1em 0;padding:.5em 1em;border-left:4px solid ${n.primary};background-color:${e};color:${n.fg};font-style:italic;`,
2637
+ PRE: `margin:1em 0;padding:12px 16px;background-color:${t};border:1px solid ${n.border};border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;line-height:1.6;overflow-x:auto;white-space:pre;color:${n.fg};`,
2638
+ HR: `border:none;border-top:1px solid ${n.border};margin:1.5em 0;`,
2639
+ TABLE: `width:100%;border-collapse:collapse;margin:1em 0;color:${n.fg};`,
2640
+ FIGURE: "margin:1em 0;text-align:center;",
2641
+ A: `display:block;text-decoration:none;margin:1em 0;padding:12px 16px;border:1px solid ${n.border};border-radius:8px;background-color:${e};color:${n.fg};`
2642
+ };
2643
+ }
2644
+ function bn(n) {
2645
+ const e = `color-mix(in srgb, ${n.fg} 5%, ${n.bg})`, t = `color-mix(in srgb, ${n.primary} 12%, transparent)`;
2646
+ return {
2647
+ "pre > code": "background:transparent;padding:0;font:inherit;color:inherit;display:block;",
2648
+ "table td": `border:1px solid ${n.border};padding:6px 10px;`,
2649
+ "table th": `border:1px solid ${n.border};padding:6px 10px;background-color:${e};font-weight:600;text-align:left;`,
2650
+ img: "max-width:100%;height:auto;border-radius:4px;",
2651
+ "a.aw-link-card .aw-link-card-text": `display:block;font-weight:600;font-size:1rem;margin-bottom:4px;color:${n.fg};text-decoration:none;`,
2652
+ "a.aw-link-card .aw-link-card-url": `display:block;font-size:.8rem;color:${n.primary};font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-decoration:none;`,
2653
+ "div.aw-youtube-embed": "position:relative;width:100%;aspect-ratio:16/9;margin:1em 0;border-radius:8px;overflow:hidden;background:#000;",
2654
+ "div.aw-youtube-embed iframe": "position:absolute;inset:0;width:100%;height:100%;border:0;display:block;",
2655
+ "a.aw-file-attachment .aw-file-icon": `flex:0 0 auto;display:inline-flex;width:36px;height:36px;align-items:center;justify-content:center;border-radius:6px;background:${t};color:${n.primary};`,
2656
+ "a.aw-file-attachment .aw-file-icon svg": "width:20px;height:20px;",
2657
+ "a.aw-file-attachment .aw-file-meta": "flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px;",
2658
+ "a.aw-file-attachment .aw-file-name": `font-weight:600;font-size:.95rem;color:${n.fg};word-break:break-all;line-height:1.3;`,
2659
+ "a.aw-file-attachment .aw-file-info": `font-size:.75rem;color:${n.fgMuted};font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;letter-spacing:.02em;`,
2660
+ "a.aw-file-attachment .aw-file-action": `flex:0 0 auto;display:inline-flex;width:32px;height:32px;align-items:center;justify-content:center;color:${n.fgMuted};`,
2661
+ "a.aw-file-attachment .aw-file-action svg": "width:18px;height:18px;",
2662
+ // ─── 코드 블록 syntax highlight 토큰 색 — getHTML() 의 portable HTML 에
2663
+ // 인라인 베이크. 에디터 CSS 의 `.aw-content pre code .hl-*` 와 같은
2664
+ // 팔레트(One Dark 다크 / Atom Light 라이트) 를 사용해 외부 컨텍스트에서도
2665
+ // 에디터·뷰어와 동일한 색.
2666
+ "pre > code .hl-keyword": n.isDark ? "color:#c678dd;font-weight:600;" : "color:#a626a4;font-weight:600;",
2667
+ "pre > code .hl-string": n.isDark ? "color:#98c379;" : "color:#50a14f;",
2668
+ "pre > code .hl-number": n.isDark ? "color:#d19a66;" : "color:#986801;",
2669
+ "pre > code .hl-comment": `color:${n.fgMuted};font-style:italic;`,
2670
+ "pre > code .hl-fn": n.isDark ? "color:#61afef;" : "color:#4078f2;",
2671
+ "pre > code .hl-builtin": n.isDark ? "color:#56b6c2;" : "color:#0184bc;",
2672
+ "pre > code .hl-tag": n.isDark ? "color:#e06c75;" : "color:#e45649;",
2673
+ "pre > code .hl-attr": n.isDark ? "color:#d19a66;" : "color:#986801;",
2674
+ "pre > code .hl-prop": n.isDark ? "color:#61afef;" : "color:#4078f2;",
2675
+ "pre > code .hl-punct": `color:${n.fgMuted};`
2676
+ };
2677
+ }
2678
+ function qe(n, e = {}) {
2679
+ const t = mn(e), o = gn(t), l = bn(t);
2680
+ for (const i of Array.from(n.children)) {
2681
+ if (!(i instanceof HTMLElement) || i.getAttribute("data-imported-html") === "true") continue;
2682
+ if (i.tagName === "A" && i.classList.contains("aw-file-attachment")) {
2683
+ oe(
2684
+ i,
2685
+ `display:flex;align-items:center;gap:12px;margin:1em 0;padding:10px 14px;border:1px solid ${t.border};border-radius:8px;background:${t.bg};color:${t.fg};text-decoration:none;`
2686
+ );
2687
+ continue;
2688
+ }
2689
+ const r = o[i.tagName];
2690
+ r && oe(i, r);
2691
+ }
2692
+ for (const [i, r] of Object.entries(l))
2693
+ for (const s of Array.from(n.querySelectorAll(i)))
2694
+ s instanceof HTMLElement && (s.closest("[data-imported-html]") || oe(s, r));
2695
+ }
2696
+ function oe(n, e) {
2697
+ const t = (n.getAttribute("style") ?? "").trim();
2698
+ if (!t) {
2699
+ n.setAttribute("style", e);
2700
+ return;
2701
+ }
2702
+ const o = e.replace(/;\s*$/, ""), l = t.replace(/;\s*$/, "");
2703
+ n.setAttribute("style", `${o};${l}`);
2704
+ }
2705
+ function Oe(n) {
2706
+ for (const e of Array.from(n.querySelectorAll("[data-block-kind]")))
2707
+ e.removeAttribute("data-block-kind");
2708
+ for (const e of Array.from(n.querySelectorAll("[data-block-label]")))
2709
+ e.removeAttribute("data-block-label");
2710
+ for (const e of Array.from(n.querySelectorAll("[data-block-focused]")))
2711
+ e.removeAttribute("data-block-focused");
2712
+ for (const e of Array.from(n.querySelectorAll("[data-block-explicit]")))
2713
+ e.removeAttribute("data-block-explicit");
2714
+ }
2715
+ function O(n) {
2716
+ const e = document.getSelection();
2717
+ if (!e) return;
2718
+ const t = document.createRange();
2719
+ if (n.tagName === "HR")
2720
+ t.setStartAfter(n);
2721
+ else {
2722
+ const o = yn(n);
2723
+ t.selectNodeContents(o), t.collapse(!0);
2724
+ }
2725
+ e.removeAllRanges(), e.addRange(t);
2726
+ }
2727
+ function yn(n) {
2728
+ switch (n.tagName) {
2729
+ case "UL":
2730
+ case "OL": {
2731
+ const e = n.querySelector(":scope > li");
2732
+ return e instanceof HTMLElement ? e : n;
2733
+ }
2734
+ case "BLOCKQUOTE": {
2735
+ const e = n.querySelector(":scope > p");
2736
+ return e instanceof HTMLElement ? e : n;
2737
+ }
2738
+ case "PRE": {
2739
+ const e = n.querySelector(":scope > code");
2740
+ return e instanceof HTMLElement ? e : n;
2741
+ }
2742
+ case "TABLE": {
2743
+ const e = n.querySelector(":scope td, :scope th");
2744
+ return e instanceof HTMLElement ? e : n;
2745
+ }
2746
+ default:
2747
+ return n;
2748
+ }
2749
+ }
2750
+ function J(n) {
2751
+ return n.appendChild(document.createElement("br")), n;
2752
+ }
2753
+ function _e(n) {
2754
+ const e = document.createElement(n), t = document.createElement("li");
2755
+ return t.appendChild(document.createElement("br")), e.appendChild(t), e;
2756
+ }
2757
+ function je(n) {
2758
+ const e = n.querySelectorAll(":scope > li");
2759
+ return e.length === 0 ? !0 : e.length === 1 ? q(e[0]) : !1;
2760
+ }
2761
+ function q(n) {
2762
+ return (n.textContent ?? "").replace(/​/g, "").trim().length === 0;
2763
+ }
2764
+ function wn(n) {
2765
+ return n === void 0 || n < 1 ? 1 : n > 6 ? 6 : n;
2766
+ }
2767
+ function Ce(n) {
2768
+ if (!n) return {};
2769
+ const e = {}, t = n.getAttribute("src"), o = n.getAttribute("alt");
2770
+ return t && (e.src = t), o && (e.alt = o), e;
2771
+ }
2772
+ const xe = {
2773
+ bulletList: "bulletList",
2774
+ orderedList: "orderedList",
2775
+ blockquote: "blockquote",
2776
+ codeBlock: "codeBlock"
2777
+ }, le = "application/x-aeriz-block";
2778
+ class $n {
2779
+ constructor(e) {
2780
+ w(this, "root");
2781
+ w(this, "content");
2782
+ w(this, "toolbar", null);
2783
+ w(this, "contentWrap");
2784
+ w(this, "handlesLayer");
2785
+ w(this, "dropIndicator");
2786
+ w(this, "handles", /* @__PURE__ */ new Map());
2787
+ w(this, "blockLabelPill");
2788
+ w(this, "statusBar");
2789
+ w(this, "statusBlockEl");
2790
+ w(this, "statusBlocksEl");
2791
+ w(this, "statusCharsEl");
2792
+ w(this, "statusSourceEl");
2793
+ w(this, "options");
2794
+ w(this, "observer");
2795
+ w(this, "blockIds", new De());
2796
+ w(this, "idFor", (e) => this.blockIds.ensure(e));
2797
+ w(this, "listeners", /* @__PURE__ */ new Set());
2798
+ w(this, "handleSelectionChange", () => this.queueSync());
2799
+ w(this, "handleResize", () => this.repositionHandles());
2800
+ w(this, "state", Tn());
2801
+ w(this, "destroyed", !1);
2802
+ w(this, "syncQueued", !1);
2803
+ w(this, "lastSerialized", "");
2804
+ /** 사용자가 색 다이얼로그에서 직접 고른 색상의 LRU. 에디터 인스턴스 메모리에만 유지. */
2805
+ w(this, "recentCustomColors", []);
2806
+ // ── 커스텀 표 셀 드래그-셀렉션 ─────────────────────────────────────
2807
+ w(this, "cellSelStart", null);
2808
+ w(this, "cellSelDrag", !1);
2809
+ /**
2810
+ * 코드 블록 input 마다 호출되는 debounced rehighlight. 활성 블록이 codeBlock
2811
+ * 이고 data-language 가 설정돼 있을 때만 동작 — 타이핑 멈춘 뒤 300ms 후 1 회.
2812
+ */
2813
+ w(this, "codeHighlightTimer", null);
2814
+ w(this, "tableResize", null);
2815
+ if (!(e.element instanceof HTMLElement))
2816
+ throw new TypeError("AerizEditor: options.element must be an HTMLElement");
2817
+ if (this.options = e, e.locale && (e.locale, void 0), this.root = document.createElement("div"), this.root.className = "aeriz-wysiwyg", e.theme && (this.root.dataset.theme = e.theme), e.toolbar !== !1) {
2818
+ const r = e.toolbar ?? Ke;
2819
+ this.toolbar = new Ve(r, {
2820
+ onAction: (s, c) => this.handleToolbarAction(s, c)
2821
+ }), this.root.appendChild(this.toolbar.element);
2822
+ }
2823
+ this.contentWrap = document.createElement("div"), this.contentWrap.className = "aw-content-wrap", this.content = document.createElement("div"), this.content.className = "aw-content", this.content.contentEditable = "true", this.content.setAttribute("role", "textbox"), this.content.setAttribute("aria-multiline", "true"), e.placeholder && (this.content.dataset.placeholder = e.placeholder), this.content.innerHTML = e.initialHTML ?? "<p><br></p>", we(this.content), ce(this.content, this.blockIds), this.lastSerialized = this.content.innerHTML, this.contentWrap.appendChild(this.content), this.handlesLayer = document.createElement("div"), this.handlesLayer.className = "aw-block-handles", this.contentWrap.appendChild(this.handlesLayer), this.dropIndicator = document.createElement("div"), this.dropIndicator.className = "aw-block-drop-indicator", this.dropIndicator.style.display = "none", this.contentWrap.appendChild(this.dropIndicator), this.blockLabelPill = document.createElement("button"), this.blockLabelPill.type = "button", this.blockLabelPill.className = "aw-block-label-pill", this.blockLabelPill.style.display = "none", this.blockLabelPill.title = "블록 유형 변경", this.blockLabelPill.addEventListener("mousedown", (r) => r.preventDefault()), this.blockLabelPill.addEventListener("click", (r) => {
2824
+ r.stopPropagation();
2825
+ const s = this.state.activeBlockId ?? void 0;
2826
+ this.openBlockTypeMenu(this.blockLabelPill, s);
2827
+ }), this.contentWrap.appendChild(this.blockLabelPill), this.root.appendChild(this.contentWrap), this.statusBar = document.createElement("div"), this.statusBar.className = "aw-status-bar";
2828
+ const t = document.createElement("div");
2829
+ t.className = "aw-status-left";
2830
+ const o = Se("되돌리기 (Ctrl+Z)", E.undo, () => {
2831
+ this.content.focus(), document.execCommand("undo");
2832
+ }), l = Se("다시 실행 (Ctrl+Y)", E.redo, () => {
2833
+ this.content.focus(), document.execCommand("redo");
2834
+ });
2835
+ this.statusBlockEl = document.createElement("span"), this.statusBlockEl.className = "aw-status-item", t.appendChild(o), t.appendChild(l), t.appendChild(this.statusBlockEl);
2836
+ const i = document.createElement("div");
2837
+ i.className = "aw-status-right", this.statusBlocksEl = document.createElement("span"), this.statusBlocksEl.className = "aw-status-item", this.statusCharsEl = document.createElement("span"), this.statusCharsEl.className = "aw-status-item", this.statusSourceEl = document.createElement("span"), this.statusSourceEl.className = "aw-status-item", i.appendChild(this.statusBlocksEl), i.appendChild(this.statusCharsEl), i.appendChild(this.statusSourceEl), this.statusBar.appendChild(t), this.statusBar.appendChild(i), this.root.appendChild(this.statusBar), e.element.innerHTML = "", e.element.appendChild(this.root), this.observer = new MutationObserver(() => this.flush()), this.observer.observe(this.content, {
2838
+ childList: !0,
2839
+ subtree: !0,
2840
+ attributes: !0,
2841
+ characterData: !0
2842
+ }), this.content.addEventListener("keyup", this.handleSelectionChange), this.content.addEventListener("mouseup", this.handleSelectionChange), this.content.addEventListener("focus", this.handleSelectionChange), this.content.addEventListener("blur", () => {
2843
+ this.handleSelectionChange(), this.rehighlightAllCodeBlocks();
2844
+ }), this.content.addEventListener("keydown", (r) => this.handleKeyDown(r)), document.addEventListener("selectionchange", this.handleSelectionChange), this.content.addEventListener("mousemove", (r) => this.handleTableCursor(r)), this.content.addEventListener("mousedown", (r) => this.handleTableResizeStart(r)), this.content.addEventListener("click", (r) => this.handleBlockAlignClick(r)), this.content.addEventListener("mousedown", (r) => this.handleCellSelStart(r)), this.content.addEventListener("mousemove", (r) => this.handleCellSelMove(r)), document.addEventListener("mouseup", () => this.handleCellSelEnd()), this.content.addEventListener("input", () => this.scheduleCodeHighlight()), this.content.addEventListener("dragstart", (r) => {
2845
+ const s = r.target;
2846
+ s && (s.closest(".aw-block-handle") || s.closest("table") && r.preventDefault());
2847
+ }), this.content.addEventListener("dragover", (r) => this.handleDragOver(r)), this.content.addEventListener("drop", (r) => this.handleDrop(r)), this.content.addEventListener("dragleave", (r) => this.handleDragLeave(r)), window.addEventListener("resize", this.handleResize), this.sync();
2848
+ }
2849
+ /**
2850
+ * 본문 HTML 을 반환한다. 외부 컨텍스트에서도 에디터에서 보이는 그대로
2851
+ * 동일한 시각으로 렌더되도록, 호출 시점의 테마 변수(--aw-primary 등) 를
2852
+ * 읽어 블록 단위 인라인 스타일에 베이크한다. 에디터 내부 DOM 자체는
2853
+ * 변형되지 않으며(clone 트리에 적용), data-block-kind / data-block-label /
2854
+ * data-block-focused 같은 에디터 전용 속성은 출력에서 제거된다.
2855
+ * data-block-id 는 보존.
2856
+ */
2857
+ getHTML() {
2858
+ const e = this.content.cloneNode(!0), t = Pe(this.root), o = this.currentTheme();
2859
+ return o && (t.mode = o), qe(e, t), Oe(e), e.innerHTML;
2860
+ }
2861
+ /** 가공 없는 원본 innerHTML. 직렬화 검증/디버그 용도. */
2862
+ getRawHTML() {
2863
+ return this.content.innerHTML;
2864
+ }
2865
+ setHTML(e) {
2866
+ this.content.innerHTML = e, this.flush();
2867
+ }
2868
+ focus() {
2869
+ this.content.focus();
2870
+ }
2871
+ setTheme(e) {
2872
+ this.root.dataset.theme = e;
2873
+ }
2874
+ getState() {
2875
+ return this.state;
2876
+ }
2877
+ getBlocks() {
2878
+ return de(this.content, this.idFor);
2879
+ }
2880
+ /**
2881
+ * 본문 블록들을 직렬화 가능한 BlockSnapshot 배열로 반환한다.
2882
+ * AerizViewer.setBlocks(snapshots) 가 받아 동일한 트리를 렌더할 수 있다.
2883
+ */
2884
+ serialize() {
2885
+ return Fe(this.getBlocks());
2886
+ }
2887
+ /**
2888
+ * data-block-id 로 본문에서 블록을 찾아 반환. 없으면 null.
2889
+ */
2890
+ findBlockById(e) {
2891
+ const t = this.content.querySelector(`[data-block-id="${e}"]`);
2892
+ return t ? ue(t, this.content, this.idFor) : null;
2893
+ }
2894
+ getActiveBlock() {
2895
+ const e = document.getSelection();
2896
+ if (!e || e.rangeCount === 0) return null;
2897
+ const t = e.getRangeAt(0).commonAncestorContainer;
2898
+ return this.content.contains(t) ? ue(t, this.content, this.idFor) : null;
2899
+ }
2900
+ /**
2901
+ * 블록 유형을 변경/추가한다.
2902
+ * - `targetBlockId` 지정 시 그 id 의 블록을 강제 대상으로 한다 (다이얼로그
2903
+ * 확인 등으로 선택이 다른 곳으로 넘어간 뒤에도 원래 블록을 정확히 변환).
2904
+ * - 미지정 시 현재 active block 사용. 둘 다 없으면 본문 끝에 새 블록 추가.
2905
+ */
2906
+ setBlockType(e, t, o) {
2907
+ const l = H[e];
2908
+ if (!l) return;
2909
+ const i = o ? this.findBlockById(o) : this.getActiveBlock(), r = i ? this.replaceBlock(i, l, t) : this.appendBlock(l, t);
2910
+ this.flush(), this.content.focus(), O(r);
2911
+ }
2912
+ replaceBlock(e, t, o) {
2913
+ const l = pn(e), i = t.create(o);
2914
+ return hn(i, t.kind, l), i.setAttribute("data-block-explicit", "true"), e.element.replaceWith(i), i;
2915
+ }
2916
+ appendBlock(e, t) {
2917
+ const o = e.create(t);
2918
+ return o.setAttribute("data-block-explicit", "true"), this.content.appendChild(o), o;
2919
+ }
2920
+ onStateChange(e) {
2921
+ return this.listeners.add(e), () => this.listeners.delete(e);
2922
+ }
2923
+ destroy() {
2924
+ this.destroyed || (this.destroyed = !0, this.observer.disconnect(), document.removeEventListener("selectionchange", this.handleSelectionChange), window.removeEventListener("resize", this.handleResize), this.handles.clear(), this.listeners.clear(), this.root.remove());
2925
+ }
2926
+ // ─── 내부 ────────────────────────────────────────────────────────────
2927
+ /**
2928
+ * DOM 변화 후 호출되는 reconcile 진입점.
2929
+ * 누락된 data-block-id 를 채우고, 자기 쓰기로 발생한 옵저버 레코드를
2930
+ * takeRecords 로 흡수한 뒤 onChange + queueSync.
2931
+ */
2932
+ flush() {
2933
+ var t, o;
2934
+ if (this.destroyed) return;
2935
+ we(this.content), ce(this.content, this.blockIds), this.observer.takeRecords();
2936
+ const e = this.content.innerHTML;
2937
+ e !== this.lastSerialized && (this.lastSerialized = e, (o = (t = this.options).onChange) == null || o.call(t, this.getHTML())), this.queueSync();
2938
+ }
2939
+ handleToolbarAction(e, t) {
2940
+ switch (e) {
2941
+ case "blockType": {
2942
+ const o = this.state.activeBlockId ?? void 0;
2943
+ this.openBlockTypeMenu(t, o);
2944
+ break;
2945
+ }
2946
+ case "fontFamily": {
2947
+ if (!Ee(this.getActiveBlock())) return;
2948
+ const o = this.detectActiveFontId();
2949
+ Mt(
2950
+ t,
2951
+ (l) => this.applyFontFamily(l.stack),
2952
+ o ?? void 0
2953
+ );
2954
+ break;
2955
+ }
2956
+ case "fontSize": {
2957
+ if (!Le(this.getActiveBlock())) return;
2958
+ const o = this.detectActiveFontSize();
2959
+ It(
2960
+ t,
2961
+ (l) => this.applyFontSize(l.px),
2962
+ o ?? void 0
2963
+ );
2964
+ break;
2965
+ }
2966
+ case "color": {
2967
+ if (!Y(this.getActiveBlock())) return;
2968
+ te(
2969
+ t,
2970
+ (o) => this.handleColorPick(o, "foreColor"),
2971
+ this.recentCustomColors
2972
+ );
2973
+ break;
2974
+ }
2975
+ case "backgroundColor": {
2976
+ if (!Y(this.getActiveBlock())) return;
2977
+ te(
2978
+ t,
2979
+ (o) => this.handleColorPick(o, "hiliteColor"),
2980
+ this.recentCustomColors
2981
+ );
2982
+ break;
2983
+ }
2984
+ case "align": {
2985
+ if (!ie(this.getActiveBlock())) return;
2986
+ Nt(t, (o) => this.applyAlign(o));
2987
+ break;
2988
+ }
2989
+ case "cellBg": {
2990
+ if (!V(this.getActiveBlock())) return;
2991
+ const o = this.getSelectedTableCells();
2992
+ if (o.length === 0) return;
2993
+ te(
2994
+ t,
2995
+ (l) => this.applyCellBgToCells(o, l),
2996
+ this.recentCustomColors
2997
+ );
2998
+ break;
2999
+ }
3000
+ case "tableMenu": {
3001
+ if (!V(this.getActiveBlock())) return;
3002
+ Rt(t, (o) => this.runTableMenu(o));
3003
+ break;
3004
+ }
3005
+ case "codeLang": {
3006
+ const o = this.getActiveBlock();
3007
+ if (!o || o.kind !== "codeBlock") return;
3008
+ const l = o.element.querySelector(":scope > code"), i = (l == null ? void 0 : l.getAttribute("data-language")) ?? void 0;
3009
+ Bt(
3010
+ t,
3011
+ (r) => this.applyCodeLanguage(o.element, r.id),
3012
+ i
3013
+ );
3014
+ break;
3015
+ }
3016
+ case "bold":
3017
+ case "italic":
3018
+ case "underline":
3019
+ case "strike":
3020
+ this.applyMark(e);
3021
+ break;
3022
+ }
3023
+ }
3024
+ /**
3025
+ * 인라인 마크(bold/italic/underline/strike) 토글. 활성 블록의 allowedMarks
3026
+ * 에 포함되지 않은 마크면 무시. styleWithCSS=true 모드로 deprecated 태그
3027
+ * (<b>/<i>/<u>) 대신 `<span style="font-weight:bold">` 형태가 만들어진다.
3028
+ *
3029
+ * 'strike' 는 execCommand 표준 명령명이 'strikeThrough'.
3030
+ */
3031
+ applyMark(e) {
3032
+ const t = this.getActiveBlock();
3033
+ if (!_(t, e)) return;
3034
+ this.content.focus(), document.execCommand("styleWithCSS", !1, "true");
3035
+ const o = e === "strike" ? "strikeThrough" : e;
3036
+ document.execCommand(o), this.queueSync();
3037
+ }
3038
+ /**
3039
+ * 색상 선택기에서 받은 색을 LRU 에 누적하고 selection 에 적용한다.
3040
+ * `command` 가 'foreColor' 면 글자색, 'hiliteColor' 면 글 배경색.
3041
+ */
3042
+ handleColorPick(e, t) {
3043
+ e && !ae.includes(e) && (this.recentCustomColors = [
3044
+ e,
3045
+ ...this.recentCustomColors.filter((o) => o !== e)
3046
+ ].slice(0, 8)), this.applyColor(t, e), this.queueSync();
3047
+ }
3048
+ /**
3049
+ * selection 에 색상을 적용. styleWithCSS=true 모드로 `<span style="color:…">`
3050
+ * 또는 `<span style="background-color:…">` 형태가 만들어진다.
3051
+ * `null` 은 '기본색' — 'inherit' 으로 부모 색을 따라가도록.
3052
+ */
3053
+ applyColor(e, t) {
3054
+ this.content.focus(), document.execCommand("styleWithCSS", !1, "true"), document.execCommand(e, !1, t ?? "inherit");
3055
+ }
3056
+ /**
3057
+ * 정렬을 활성 블록(또는 표 셀) 에 적용한다.
3058
+ *
3059
+ * execCommand('justify*') 는 일부 브라우저에서 blockquote 안의 <p> 에 적용
3060
+ * 시 blockquote 를 <div style="text-align:..."> 로 바꿔버리는 잘 알려진
3061
+ * 버그가 있어 직접 DOM 조작으로 대체한다.
3062
+ *
3063
+ * 적용 대상:
3064
+ * paragraph / heading → element 자체
3065
+ * blockquote → 안의 <p> (없으면 자기 자신)
3066
+ * table 단일 셀 → 캐럿이 들어 있는 td/th
3067
+ * table 다중 셀 마킹 → 각 cell.style.textAlign 일괄
3068
+ */
3069
+ applyAlign(e) {
3070
+ const t = this.getActiveBlock();
3071
+ if (!t || !ie(t)) return;
3072
+ const o = Array.from(
3073
+ this.content.querySelectorAll(
3074
+ "td.aw-cell-selected, th.aw-cell-selected"
3075
+ )
3076
+ );
3077
+ if (o.length > 0) {
3078
+ for (const i of o) i.style.textAlign = e;
3079
+ this.clearCellSelection(), this.queueSync();
3080
+ return;
3081
+ }
3082
+ let l = t.element;
3083
+ if (t.kind === "blockquote") {
3084
+ const i = t.element.querySelector(":scope > p");
3085
+ i instanceof HTMLElement && (l = i);
3086
+ } else if (t.kind === "table") {
3087
+ const i = this.getActiveTableCell();
3088
+ i && (l = i);
3089
+ }
3090
+ l.style.textAlign = e, this.queueSync();
3091
+ }
3092
+ /** 캐럿이 들어 있는 표 셀(td/th) 반환. 표 밖이면 null. */
3093
+ getActiveTableCell() {
3094
+ const e = document.getSelection();
3095
+ if (!e || e.rangeCount === 0) return null;
3096
+ let t = e.getRangeAt(0).startContainer;
3097
+ if (!t || !this.content.contains(t) || (t.nodeType !== Node.ELEMENT_NODE && (t = t.parentElement), !(t instanceof Element))) return null;
3098
+ const o = t.closest("td, th");
3099
+ return o instanceof HTMLTableCellElement ? o : null;
3100
+ }
3101
+ /**
3102
+ * 현재 활성 셀(들) 을 반환한다.
3103
+ * 1순위: 커스텀 드래그-셀렉션으로 마킹된 `.aw-cell-selected` 셀들
3104
+ * 2순위: 브라우저 selection range 가 가리키는 표 셀(들)
3105
+ * 3순위: 캐럿이 들어 있는 단일 셀
3106
+ */
3107
+ getSelectedTableCells() {
3108
+ const e = Array.from(
3109
+ this.content.querySelectorAll("td.aw-cell-selected, th.aw-cell-selected")
3110
+ );
3111
+ if (e.length > 0) return e;
3112
+ const t = this.getActiveBlock();
3113
+ if (!t || t.kind !== "table") return [];
3114
+ const o = t.element, l = document.getSelection();
3115
+ if (!l || l.rangeCount === 0) return [];
3116
+ const i = l.getRangeAt(0), r = G(i.startContainer);
3117
+ if (!r || !o.contains(r)) return [];
3118
+ if (i.collapsed) return [r];
3119
+ const s = G(i.endContainer);
3120
+ return !s || !o.contains(s) || s === r ? [r] : re(o, r, s);
3121
+ }
3122
+ handleCellSelStart(e) {
3123
+ if (e.button !== 0) return;
3124
+ const t = e.target;
3125
+ if (t != null && t.closest(
3126
+ ".aw-resize-handle, .aw-block-align-controls, .aw-file-attachment, .aw-link-card"
3127
+ ))
3128
+ return;
3129
+ this.clearCellSelection();
3130
+ const o = t == null ? void 0 : t.closest("td, th");
3131
+ o instanceof HTMLTableCellElement && this.content.contains(o) && (this.cellSelStart = o, this.cellSelDrag = !1);
3132
+ }
3133
+ handleCellSelMove(e) {
3134
+ if (!this.cellSelStart || !(e.buttons & 1)) return;
3135
+ const t = e.target, o = t == null ? void 0 : t.closest("td, th");
3136
+ if (!(o instanceof HTMLTableCellElement) || o === this.cellSelStart && !this.cellSelDrag) return;
3137
+ const l = o.closest("table");
3138
+ if (!(l instanceof HTMLTableElement)) return;
3139
+ const i = re(l, this.cellSelStart, o);
3140
+ if (!(i.length <= 1)) {
3141
+ this.cellSelDrag = !0, this.clearCellSelection();
3142
+ for (const r of i) r.classList.add("aw-cell-selected");
3143
+ }
3144
+ }
3145
+ handleCellSelEnd() {
3146
+ this.cellSelStart = null, this.cellSelDrag = !1;
3147
+ }
3148
+ /** `.aw-cell-selected` 마킹을 모두 제거. */
3149
+ clearCellSelection() {
3150
+ const e = this.content.querySelectorAll(".aw-cell-selected");
3151
+ for (const t of Array.from(e)) t.classList.remove("aw-cell-selected");
3152
+ }
3153
+ /**
3154
+ * 표 셀 배열 각각에 `cell.style.fontSize` 를 직접 설정. Range API 의 wrap
3155
+ * 경로(셀 중첩 버그 발생) 를 우회하기 위한 안전한 다중 셀 적용 경로.
3156
+ */
3157
+ applyFontSizeToCells(e, t) {
3158
+ let o = 0;
3159
+ for (const l of e)
3160
+ l.isConnected && (l.style.fontSize = `${t}px`, o++);
3161
+ o !== 0 && (this.clearCellSelection(), this.flush());
3162
+ }
3163
+ /**
3164
+ * 캡처해 둔 셀(들) 에 배경색을 일괄 적용. null 이면 inline background-color
3165
+ * 제거. `<th>` 의 stylesheet 배경(0,1,2 specificity) 보다 inline + !important
3166
+ * 가 항상 우선하도록 setProperty('important') 로 박는다.
3167
+ */
3168
+ applyCellBgToCells(e, t) {
3169
+ let o = 0;
3170
+ for (const l of e)
3171
+ l.isConnected && (t ? l.style.setProperty("background-color", t, "important") : l.style.removeProperty("background-color"), o++);
3172
+ o !== 0 && (t && !ae.includes(t) && (this.recentCustomColors = [
3173
+ t,
3174
+ ...this.recentCustomColors.filter((l) => l !== t)
3175
+ ].slice(0, 8)), this.clearCellSelection(), this.flush());
3176
+ }
3177
+ /** 표 편집 드롭다운에서 받은 액션을 분기 실행. */
3178
+ runTableMenu(e) {
3179
+ switch (e) {
3180
+ case "rowAbove":
3181
+ this.insertRow("above");
3182
+ break;
3183
+ case "rowBelow":
3184
+ this.insertRow("below");
3185
+ break;
3186
+ case "colLeft":
3187
+ this.insertColumn("left");
3188
+ break;
3189
+ case "colRight":
3190
+ this.insertColumn("right");
3191
+ break;
3192
+ case "deleteRow":
3193
+ this.deleteRow();
3194
+ break;
3195
+ case "deleteCol":
3196
+ this.deleteColumn();
3197
+ break;
3198
+ }
3199
+ }
3200
+ /** 활성 셀의 행 위/아래에 새 행 삽입. 같은 열 수, 빈 td 로 채움. */
3201
+ insertRow(e) {
3202
+ const t = this.getActiveTableCell();
3203
+ if (!t) return;
3204
+ const o = t.closest("tr");
3205
+ if (!o || !o.parentNode) return;
3206
+ const l = o.children.length, i = document.createElement("tr");
3207
+ for (let r = 0; r < l; r++) {
3208
+ const s = document.createElement("td");
3209
+ s.appendChild(document.createElement("br")), i.appendChild(s);
3210
+ }
3211
+ e === "above" ? o.parentNode.insertBefore(i, o) : o.parentNode.insertBefore(i, o.nextSibling), this.flush();
3212
+ }
3213
+ /** 활성 셀이 속한 열의 좌/우에 새 열 삽입. 모든 행에 빈 td 추가. */
3214
+ insertColumn(e) {
3215
+ const t = this.getActiveTableCell();
3216
+ if (!t) return;
3217
+ const o = t.closest("tr");
3218
+ if (!o) return;
3219
+ const l = Array.from(o.children).indexOf(t);
3220
+ if (l < 0) return;
3221
+ const i = o.closest("table");
3222
+ if (i) {
3223
+ for (const r of Array.from(i.querySelectorAll("tr"))) {
3224
+ const s = r.children[l];
3225
+ if (!s) continue;
3226
+ const c = s.tagName.toLowerCase(), a = document.createElement(c);
3227
+ a.appendChild(document.createElement("br")), e === "left" ? r.insertBefore(a, s) : r.insertBefore(a, s.nextSibling);
3228
+ }
3229
+ this.flush();
3230
+ }
3231
+ }
3232
+ /** 활성 셀의 행 전체 삭제. 마지막 행이면 보존(빈 표 회피). */
3233
+ deleteRow() {
3234
+ const e = this.getActiveTableCell();
3235
+ if (!e) return;
3236
+ const t = e.closest("tr");
3237
+ if (!t) return;
3238
+ const o = t.closest("table");
3239
+ o && (o.querySelectorAll("tr").length <= 1 || (t.remove(), this.flush()));
3240
+ }
3241
+ scheduleCodeHighlight() {
3242
+ this.codeHighlightTimer !== null && window.clearTimeout(this.codeHighlightTimer), this.codeHighlightTimer = window.setTimeout(() => {
3243
+ this.codeHighlightTimer = null;
3244
+ const e = this.getActiveBlock();
3245
+ if ((e == null ? void 0 : e.kind) !== "codeBlock") return;
3246
+ const t = e.element.querySelector(":scope > code");
3247
+ if (!(t instanceof HTMLElement)) return;
3248
+ const o = t.getAttribute("data-language");
3249
+ o && this.rehighlightCode(t, o);
3250
+ }, 300);
3251
+ }
3252
+ /**
3253
+ * code 블록의 언어를 변경하고 즉시 highlight 를 다시 적용. 캐럿 위치는
3254
+ * 텍스트 offset 으로 보존.
3255
+ */
3256
+ applyCodeLanguage(e, t) {
3257
+ const o = e.querySelector(":scope > code");
3258
+ o instanceof HTMLElement && (t === "plaintext" ? o.removeAttribute("data-language") : o.setAttribute("data-language", t), this.rehighlightCode(o, t), this.flush());
3259
+ }
3260
+ /**
3261
+ * `<code>` element 의 텍스트를 highlight 후 innerHTML 로 갱신. 셀렉션이
3262
+ * code 안에 있다면 textContent offset 으로 caret 위치 보존.
3263
+ */
3264
+ rehighlightCode(e, t) {
3265
+ const o = e.textContent ?? "", l = xn(e);
3266
+ e.innerHTML = wt(o, t), l && En(e, l);
3267
+ }
3268
+ /**
3269
+ * 본문에서 모든 codeBlock 을 순회하며 highlight 적용. setHTML / 초기 로드
3270
+ * / 외부 import 후 호출.
3271
+ */
3272
+ rehighlightAllCodeBlocks() {
3273
+ const e = this.content.querySelectorAll(":scope > pre > code");
3274
+ for (const t of Array.from(e)) {
3275
+ const o = t.getAttribute("data-language") ?? void 0;
3276
+ o && this.rehighlightCode(t, o);
3277
+ }
3278
+ }
3279
+ /** 활성 셀의 열 전체 삭제. 마지막 열이면 보존(빈 표 회피). */
3280
+ deleteColumn() {
3281
+ var r;
3282
+ const e = this.getActiveTableCell();
3283
+ if (!e) return;
3284
+ const t = e.closest("tr");
3285
+ if (!t) return;
3286
+ const o = Array.from(t.children).indexOf(e);
3287
+ if (o < 0) return;
3288
+ const l = t.closest("table");
3289
+ if (!l) return;
3290
+ const i = l.querySelector("tr");
3291
+ if (!(i && i.children.length <= 1)) {
3292
+ for (const s of Array.from(l.querySelectorAll("tr")))
3293
+ (r = s.children[o]) == null || r.remove();
3294
+ this.flush();
3295
+ }
3296
+ }
3297
+ /**
3298
+ * 마우스 이동 시 셀 우/하 가장자리에서 col-resize / row-resize 커서 표시.
3299
+ * 드래그 진행 중에는 별도 핸들러가 cursor 를 관리하므로 skip.
3300
+ */
3301
+ handleTableCursor(e) {
3302
+ var l, i;
3303
+ if (this.tableResize) return;
3304
+ const t = (i = (l = e.target) == null ? void 0 : l.closest) == null ? void 0 : i.call(l, "td, th");
3305
+ if (!(t instanceof HTMLTableCellElement)) {
3306
+ this.content.style.cursor = "";
3307
+ return;
3308
+ }
3309
+ const o = Ae(t, e.clientX, e.clientY);
3310
+ this.content.style.cursor = o === "right" ? "col-resize" : o === "bottom" ? "row-resize" : "";
3311
+ }
3312
+ /**
3313
+ * 셀 가장자리 / 이미지·유튜브 리사이즈 핸들 mousedown 진입점.
3314
+ * - 셀: 우(col-resize) / 하(row-resize) 가장자리 6px 영역
3315
+ * - 이미지·유튜브: 우하단 `.aw-resize-handle` element (corner resize)
3316
+ */
3317
+ handleTableResizeStart(e) {
3318
+ var c;
3319
+ if (e.button !== 0) return;
3320
+ const t = e.target, o = t == null ? void 0 : t.closest(".aw-resize-handle");
3321
+ if (o instanceof HTMLElement) {
3322
+ const a = o.parentElement;
3323
+ if (a instanceof HTMLElement && (a.tagName === "FIGURE" || a.tagName === "DIV" && a.classList.contains("aw-youtube-embed"))) {
3324
+ e.preventDefault(), e.stopPropagation(), this.startBlockResize(a, e.clientX);
3325
+ return;
3326
+ }
3327
+ }
3328
+ const l = (c = t == null ? void 0 : t.closest) == null ? void 0 : c.call(t, "td, th");
3329
+ if (!(l instanceof HTMLTableCellElement)) return;
3330
+ const i = Ae(l, e.clientX, e.clientY);
3331
+ if (!i) return;
3332
+ if (e.preventDefault(), e.stopPropagation(), i === "right") {
3333
+ const a = l.closest("tr"), d = a ? Array.from(a.children).indexOf(l) : -1, u = l.closest("table");
3334
+ if (!u || d < 0) return;
3335
+ const m = [];
3336
+ for (const f of Array.from(u.querySelectorAll("tr"))) {
3337
+ const p = f.children[d];
3338
+ p instanceof HTMLTableCellElement && m.push(p);
3339
+ }
3340
+ this.tableResize = {
3341
+ kind: "col",
3342
+ startX: e.clientX,
3343
+ startY: e.clientY,
3344
+ initialSize: l.getBoundingClientRect().width,
3345
+ targetCells: m,
3346
+ targetRow: null
3347
+ }, document.body.style.cursor = "col-resize";
3348
+ } else {
3349
+ const a = l.closest("tr");
3350
+ if (!(a instanceof HTMLTableRowElement)) return;
3351
+ this.tableResize = {
3352
+ kind: "row",
3353
+ startX: e.clientX,
3354
+ startY: e.clientY,
3355
+ initialSize: a.getBoundingClientRect().height,
3356
+ targetCells: [],
3357
+ targetRow: a
3358
+ }, document.body.style.cursor = "row-resize";
3359
+ }
3360
+ const r = (a) => this.handleTableResizeMove(a), s = () => {
3361
+ document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", s), document.body.style.cursor = "", this.tableResize = null, this.flush();
3362
+ };
3363
+ document.addEventListener("mousemove", r), document.addEventListener("mouseup", s);
3364
+ }
3365
+ handleTableResizeMove(e) {
3366
+ const t = this.tableResize;
3367
+ if (t) {
3368
+ if (t.kind === "col") {
3369
+ const o = e.clientX - t.startX, l = Math.max(40, t.initialSize + o), i = `${Math.round(l)}px`;
3370
+ for (const r of t.targetCells) r.style.width = i;
3371
+ } else if (t.targetRow) {
3372
+ const o = e.clientY - t.startY, l = Math.max(20, t.initialSize + o);
3373
+ t.targetRow.style.height = `${Math.round(l)}px`;
3374
+ }
3375
+ }
3376
+ }
3377
+ /**
3378
+ * 이미지/유튜브 좌상단 정렬 컨트롤 클릭 핸들러. data-align 에 따라 wrapper
3379
+ * 의 marginLeft / marginRight 를 조정 — left/center/right.
3380
+ */
3381
+ handleBlockAlignClick(e) {
3382
+ var i, r;
3383
+ const t = (r = (i = e.target) == null ? void 0 : i.closest) == null ? void 0 : r.call(i, ".aw-block-align-btn");
3384
+ if (!(t instanceof HTMLButtonElement)) return;
3385
+ const o = t.dataset.align;
3386
+ if (o !== "left" && o !== "center" && o !== "right") return;
3387
+ const l = t.closest("figure, div.aw-youtube-embed");
3388
+ l instanceof HTMLElement && (e.preventDefault(), e.stopPropagation(), o === "center" ? (l.style.marginLeft = "auto", l.style.marginRight = "auto") : o === "right" ? (l.style.marginLeft = "auto", l.style.marginRight = "0") : (l.style.marginLeft = "0", l.style.marginRight = "auto"), this.flush());
3389
+ }
3390
+ /**
3391
+ * 이미지 / 유튜브 wrapper 의 너비를 드래그로 조정. 높이는 자동 (이미지는
3392
+ * 종횡비 유지, 유튜브는 CSS aspect-ratio 16/9). mouseup 에서 cursor 복귀
3393
+ * + flush.
3394
+ */
3395
+ startBlockResize(e, t) {
3396
+ const o = e.getBoundingClientRect().width;
3397
+ document.body.style.cursor = "nwse-resize", e.classList.add("aw-resizing");
3398
+ const l = (r) => {
3399
+ const s = r.clientX - t, c = Math.max(80, o + s);
3400
+ e.style.width = `${Math.round(c)}px`;
3401
+ }, i = () => {
3402
+ document.removeEventListener("mousemove", l), document.removeEventListener("mouseup", i), document.body.style.cursor = "", e.classList.remove("aw-resizing"), this.flush();
3403
+ };
3404
+ document.addEventListener("mousemove", l), document.addEventListener("mouseup", i);
3405
+ }
3406
+ currentTheme() {
3407
+ const e = this.root.dataset.theme;
3408
+ return e === "dark" || e === "light" ? e : void 0;
3409
+ }
3410
+ /**
3411
+ * 선택 영역에 글꼴을 적용한다.
3412
+ * `styleWithCSS=true` 모드의 execCommand('fontName') 가 `<font face=…>` 대신
3413
+ * `<span style="font-family: …">` 를 생성하도록 강제한다. selection 이
3414
+ * 비어 있으면(캐럿만) 다음 입력 글꼴이 결정된다(브라우저 기본 동작).
3415
+ */
3416
+ applyFontFamily(e) {
3417
+ this.content.focus(), document.execCommand("styleWithCSS", !1, "true"), document.execCommand("fontName", !1, e), this.queueSync();
3418
+ }
3419
+ /**
3420
+ * 현재 selection 의 anchor 노드 기준 computed font-family 를 읽어 알려진
3421
+ * FONT_FAMILIES id 와 매칭. 글꼴 드롭다운의 active 표시에 사용.
3422
+ */
3423
+ detectActiveFontId() {
3424
+ const e = document.getSelection();
3425
+ if (!e || e.rangeCount === 0) return null;
3426
+ let t = e.getRangeAt(0).startContainer;
3427
+ if (!t || !this.content.contains(t) || (t.nodeType !== Node.ELEMENT_NODE && (t = t.parentElement), !(t instanceof Element))) return null;
3428
+ const o = getComputedStyle(t).fontFamily;
3429
+ return it(o);
3430
+ }
3431
+ /**
3432
+ * 선택 영역에 글자 크기를 적용한다. execCommand('fontSize') 는 1~7 만 받기
3433
+ * 때문에 px 적용을 위해 Range API 로 직접 `<span style="font-size: Xpx">`
3434
+ * wrap 한다.
3435
+ *
3436
+ * **다중 셀 선택**(커스텀 마킹 또는 셀 경계를 넘는 browser range) 의 경우엔
3437
+ * Range.extractContents 가 셀들을 묶어 한 셀 안에 끼워넣는 셀 중첩 버그가
3438
+ * 있어 — 셀별로 `cell.style.fontSize` 를 직접 설정하는 안전한 경로를 사용.
3439
+ */
3440
+ applyFontSize(e) {
3441
+ const t = Array.from(
3442
+ this.content.querySelectorAll(
3443
+ "td.aw-cell-selected, th.aw-cell-selected"
3444
+ )
3445
+ );
3446
+ if (t.length > 0) {
3447
+ this.applyFontSizeToCells(t, e);
3448
+ return;
3449
+ }
3450
+ const o = document.getSelection();
3451
+ if (o && o.rangeCount > 0 && !o.getRangeAt(0).collapsed) {
3452
+ const s = o.getRangeAt(0), c = G(s.startContainer), a = G(s.endContainer);
3453
+ if (c && a && c !== a && this.content.contains(c) && this.content.contains(a)) {
3454
+ const d = c.closest("table");
3455
+ if (d instanceof HTMLTableElement) {
3456
+ this.applyFontSizeToCells(re(d, c, a), e);
3457
+ return;
3458
+ }
3459
+ }
3460
+ }
3461
+ this.content.focus();
3462
+ const l = document.getSelection();
3463
+ if (!l || l.rangeCount === 0) return;
3464
+ const i = l.getRangeAt(0);
3465
+ if (!this.content.contains(i.commonAncestorContainer)) return;
3466
+ const r = document.createElement("span");
3467
+ if (r.style.fontSize = `${e}px`, i.collapsed) {
3468
+ r.appendChild(document.createTextNode("​")), i.insertNode(r);
3469
+ const s = document.createRange();
3470
+ s.selectNodeContents(r), s.collapse(!1), l.removeAllRanges(), l.addRange(s);
3471
+ } else {
3472
+ try {
3473
+ i.surroundContents(r);
3474
+ } catch {
3475
+ const c = i.extractContents();
3476
+ r.appendChild(c), i.insertNode(r);
3477
+ }
3478
+ l.removeAllRanges();
3479
+ const s = document.createRange();
3480
+ s.selectNodeContents(r), l.addRange(s);
3481
+ }
3482
+ this.queueSync();
3483
+ }
3484
+ /**
3485
+ * 현재 selection 의 anchor 노드 기준 computed font-size 를 px 정수로 읽어
3486
+ * FONT_SIZES 의 값과 일치할 때만 반환. 글자 크기 드롭다운의 active 표시에
3487
+ * 사용.
3488
+ */
3489
+ detectActiveFontSize() {
3490
+ const e = document.getSelection();
3491
+ if (!e || e.rangeCount === 0) return null;
3492
+ let t = e.getRangeAt(0).startContainer;
3493
+ if (!t || !this.content.contains(t) || (t.nodeType !== Node.ELEMENT_NODE && (t = t.parentElement), !(t instanceof Element))) return null;
3494
+ const o = getComputedStyle(t).fontSize;
3495
+ return ot(o);
3496
+ }
3497
+ /**
3498
+ * 블록 유형 드롭다운을 열어 사용자 선택 결과를 처리한다.
3499
+ * 툴바의 blockType 버튼 / 활성 블록 라벨 pill 양쪽 진입점이 동일한 분기
3500
+ * 로직(리치 블록 다이얼로그 분기 + targetId 보존) 을 공유한다.
3501
+ */
3502
+ openBlockTypeMenu(e, t) {
3503
+ Tt(e, (o) => {
3504
+ if (o.kind === "linkCard") {
3505
+ this.openLinkDialogAndApply(t);
3506
+ return;
3507
+ }
3508
+ if (o.kind === "youtube") {
3509
+ this.openYouTubeDialogAndApply(t);
3510
+ return;
3511
+ }
3512
+ if (o.kind === "image") {
3513
+ this.openImageDialogAndApply(t);
3514
+ return;
3515
+ }
3516
+ if (o.kind === "table") {
3517
+ zt(e, (l) => {
3518
+ this.setBlockType(
3519
+ "table",
3520
+ { rows: l.rows, cols: l.cols },
3521
+ t
3522
+ );
3523
+ });
3524
+ return;
3525
+ }
3526
+ if (o.kind === "importHtml") {
3527
+ this.openImportHtmlDialogAndApply(t);
3528
+ return;
3529
+ }
3530
+ if (o.kind === "importMarkdown") {
3531
+ this.openMarkdownImportDialogAndApply(t);
3532
+ return;
3533
+ }
3534
+ if (o.kind === "file") {
3535
+ this.openFileAttachmentDialogAndApply(t);
3536
+ return;
3537
+ }
3538
+ this.setBlockType(o.kind, o.attrs, t);
3539
+ });
3540
+ }
3541
+ /**
3542
+ * 링크 다이얼로그를 열어 URL/텍스트를 받은 뒤 linkCard 블록으로 변환/추가.
3543
+ * 표시 텍스트가 비어 있으면 URL 을 텍스트로 사용. `targetId` 가 있으면
3544
+ * 그 id 의 블록을 정확히 변환 대상으로 삼는다.
3545
+ */
3546
+ openLinkDialogAndApply(e) {
3547
+ Ge({}, this.currentTheme()).then((t) => {
3548
+ var r;
3549
+ if (!t.confirmed || !t.href) return;
3550
+ const o = t.href, l = ((r = t.text) == null ? void 0 : r.trim()) ?? "", i = l.length > 0 ? l : o;
3551
+ this.setBlockType("linkCard", { href: o, text: i }, e);
3552
+ });
3553
+ }
3554
+ /**
3555
+ * 이미지 다이얼로그(임베드/URL/업로드)를 열어 src 를 받은 뒤 image 블록으로
3556
+ * 변환/추가.
3557
+ */
3558
+ openImageDialogAndApply(e) {
3559
+ const t = this.resolveImageUploadFn();
3560
+ Ze(
3561
+ { ...t ? { onUpload: t } : {} },
3562
+ this.currentTheme()
3563
+ ).then((o) => {
3564
+ o.confirmed && o.src && this.setBlockType("image", { src: o.src }, e), this.queueSync();
3565
+ });
3566
+ }
3567
+ /**
3568
+ * 유튜브 다이얼로그를 열어 URL 을 받고, 영상 ID 를 추출해 youtube 블록으로
3569
+ * 변환/추가. 잘못된 URL 이면 무시.
3570
+ */
3571
+ openYouTubeDialogAndApply(e) {
3572
+ et(this.currentTheme()).then((t) => {
3573
+ if (!t.confirmed || !t.url) return;
3574
+ const o = Ln(t.url);
3575
+ o && this.setBlockType("youtube", { videoId: o }, e);
3576
+ });
3577
+ }
3578
+ /**
3579
+ * HTML 가져오기 다이얼로그를 열어 사용자가 고른 HTML 을 iframe 으로 로드/
3580
+ * 처리한 뒤 본문에 element 들을 삽입한다.
3581
+ *
3582
+ * - `targetId` 가 있으면 그 블록 바로 뒤에 삽입, 없으면 본문 끝에 append.
3583
+ * - 삽입된 element 는 MutationObserver flush 가 새 data-block-id 를 발급.
3584
+ */
3585
+ openImportHtmlDialogAndApply(e) {
3586
+ Xe(this.currentTheme()).then(async (t) => {
3587
+ if (!t.confirmed) return;
3588
+ if (!t.html || t.html.length === 0) {
3589
+ window.alert("가져올 HTML 이 비어 있습니다.");
3590
+ return;
3591
+ }
3592
+ let o;
3593
+ try {
3594
+ o = await Dt(t.html);
3595
+ } catch (s) {
3596
+ console.error("HTML 가져오기 실패:", s);
3597
+ const c = s instanceof Error ? s.message : String(s);
3598
+ window.alert(`HTML 가져오기 실패: ${c}`);
3599
+ return;
3600
+ }
3601
+ if (o.length === 0) {
3602
+ window.alert("HTML 에서 가져올 본문이 없습니다.");
3603
+ return;
3604
+ }
3605
+ const l = e ? this.content.querySelector(`[data-block-id="${e}"]`) : null, i = [];
3606
+ for (const s of o) {
3607
+ const c = Mn(s.tagName) ? s : (() => {
3608
+ const a = document.createElement("p");
3609
+ return a.appendChild(s), a;
3610
+ })();
3611
+ c.setAttribute("data-block-explicit", "true"), i.push(c);
3612
+ }
3613
+ if (l)
3614
+ if (l.tagName === "P" && (l.textContent ?? "").trim().length === 0 && i.length > 0) {
3615
+ const c = i[0];
3616
+ l.replaceWith(c);
3617
+ let a = c;
3618
+ for (let d = 1; d < i.length; d++) {
3619
+ const u = i[d];
3620
+ a.after(u), a = u;
3621
+ }
3622
+ } else {
3623
+ let c = l;
3624
+ for (const a of i)
3625
+ c.after(a), c = a;
3626
+ }
3627
+ else
3628
+ for (const s of i) this.content.appendChild(s);
3629
+ this.flush(), this.content.focus();
3630
+ const r = i[i.length - 1];
3631
+ r && O(r);
3632
+ });
3633
+ }
3634
+ /**
3635
+ * Markdown 가져오기 다이얼로그를 열어 사용자가 입력한 markdown 을 파싱해
3636
+ * 본문에 element 들을 삽입한다. importHtml 과 동일한 삽입 정책을 따른다.
3637
+ */
3638
+ openMarkdownImportDialogAndApply(e) {
3639
+ Qe(this.currentTheme()).then((t) => {
3640
+ if (!t.confirmed) return;
3641
+ if (!t.markdown || t.markdown.length === 0) {
3642
+ window.alert("가져올 Markdown 이 비어 있습니다.");
3643
+ return;
3644
+ }
3645
+ let o;
3646
+ try {
3647
+ o = Pt(t.markdown);
3648
+ } catch (r) {
3649
+ console.error("Markdown 파싱 실패:", r);
3650
+ const s = r instanceof Error ? r.message : String(r);
3651
+ window.alert(`Markdown 가져오기 실패: ${s}`);
3652
+ return;
3653
+ }
3654
+ if (o.length === 0) {
3655
+ window.alert("Markdown 에서 가져올 본문이 없습니다.");
3656
+ return;
3657
+ }
3658
+ for (const r of o) r.setAttribute("data-block-explicit", "true");
3659
+ const l = e ? this.content.querySelector(`[data-block-id="${e}"]`) : null;
3660
+ if (l)
3661
+ if (l.tagName === "P" && (l.textContent ?? "").trim().length === 0 && o.length > 0) {
3662
+ const s = o[0];
3663
+ l.replaceWith(s);
3664
+ let c = s;
3665
+ for (let a = 1; a < o.length; a++) {
3666
+ const d = o[a];
3667
+ c.after(d), c = d;
3668
+ }
3669
+ } else {
3670
+ let s = l;
3671
+ for (const c of o)
3672
+ s.after(c), s = c;
3673
+ }
3674
+ else
3675
+ for (const r of o) this.content.appendChild(r);
3676
+ this.flush(), this.content.focus();
3677
+ const i = o[o.length - 1];
3678
+ i && O(i);
3679
+ });
3680
+ }
3681
+ /**
3682
+ * 파일 첨부 다이얼로그(URL/업로드)를 열어 src 를 받은 뒤 file 블록으로
3683
+ * 변환/추가. src 가 비어 있으면 무시. 파일명이 없으면 URL 의 마지막
3684
+ * segment 를 fallback 으로 사용.
3685
+ */
3686
+ openFileAttachmentDialogAndApply(e) {
3687
+ const t = this.resolveFileUploadFn();
3688
+ Je(
3689
+ { ...t ? { onUpload: t } : {} },
3690
+ this.currentTheme()
3691
+ ).then((o) => {
3692
+ if (!o.confirmed) return;
3693
+ if (!o.src || o.src.length === 0) {
3694
+ window.alert("파일 URL 이 비어 있습니다.");
3695
+ return;
3696
+ }
3697
+ const l = { fileSrc: o.src }, i = o.name && o.name.length > 0 ? o.name : An(o.src);
3698
+ i && (l.fileName = i), o.size !== void 0 && (l.fileSize = o.size), o.type && (l.fileType = o.type), this.setBlockType("file", l, e);
3699
+ });
3700
+ }
3701
+ /**
3702
+ * 파일 업로드 핸들러를 결정. onFileUpload 가 우선, 없으면 fileUploadUrl 에
3703
+ * multipart/form-data POST 후 응답 JSON 의 `url` 필드를 사용. 둘 다 없으면
3704
+ * undefined → 다이얼로그 업로드 탭 비활성.
3705
+ */
3706
+ resolveFileUploadFn() {
3707
+ if (this.options.onFileUpload) return this.options.onFileUpload;
3708
+ const e = this.options.fileUploadUrl;
3709
+ if (e)
3710
+ return async (t) => {
3711
+ const o = new FormData();
3712
+ o.append("file", t);
3713
+ const l = await fetch(e, { method: "POST", body: o });
3714
+ if (!l.ok) throw new Error(`파일 업로드 실패: ${l.status}`);
3715
+ const i = await l.json(), r = typeof i == "object" && i && "url" in i ? i.url : void 0;
3716
+ if (typeof r != "string" || r.length === 0)
3717
+ throw new Error("파일 업로드 응답에 url 필드가 없습니다.");
3718
+ return r;
3719
+ };
3720
+ }
3721
+ /**
3722
+ * 이미지 업로드 핸들러를 결정. options.onImageUpload 가 우선이며, 없으면
3723
+ * options.imageUploadUrl 로 multipart/form-data POST 후 응답 JSON 의
3724
+ * `url` 필드를 사용. 둘 다 없으면 undefined → 다이얼로그 업로드 탭 비활성.
3725
+ */
3726
+ resolveImageUploadFn() {
3727
+ if (this.options.onImageUpload) return this.options.onImageUpload;
3728
+ const e = this.options.imageUploadUrl;
3729
+ if (e)
3730
+ return async (t) => {
3731
+ const o = new FormData();
3732
+ o.append("file", t);
3733
+ const l = await fetch(e, { method: "POST", body: o });
3734
+ if (!l.ok) throw new Error(`이미지 업로드 실패: ${l.status}`);
3735
+ const i = await l.json(), r = typeof i == "object" && i && "url" in i ? i.url : void 0;
3736
+ if (typeof r != "string" || r.length === 0)
3737
+ throw new Error("이미지 업로드 응답에 url 필드가 없습니다.");
3738
+ return r;
3739
+ };
3740
+ }
3741
+ /**
3742
+ * `src` 의 이미지를 figure>img 블록으로 본문에 삽입.
3743
+ * 활성 블록이 있으면 그 블록을 image 로 변환, 없으면 본문 끝에 추가.
3744
+ */
3745
+ insertImageBlock(e) {
3746
+ this.setBlockType("image", { src: e });
3747
+ }
3748
+ queueSync() {
3749
+ this.syncQueued || this.destroyed || (this.syncQueued = !0, queueMicrotask(() => {
3750
+ this.syncQueued = !1, !this.destroyed && this.sync();
3751
+ }));
3752
+ }
3753
+ sync() {
3754
+ const e = de(this.content, this.idFor), t = this.getActiveBlock(), l = t !== null ? {
3755
+ bold: Z("bold"),
3756
+ italic: Z("italic"),
3757
+ underline: Z("underline"),
3758
+ strike: Z("strikeThrough")
3759
+ } : { bold: !1, italic: !1, underline: !1, strike: !1 };
3760
+ this.state = {
3761
+ blocks: e,
3762
+ activeBlockId: (t == null ? void 0 : t.id) ?? null,
3763
+ inline: l
3764
+ }, this.syncBlockMarkers((t == null ? void 0 : t.id) ?? null), this.syncBlockHandles(e), this.syncBlockLabelPill(t), this.syncStatusBar(t, e), this.applyToolbarState(this.state, t);
3765
+ for (const i of this.listeners)
3766
+ i(this.state);
3767
+ }
3768
+ // ─── 키보드 ──────────────────────────────────────────────────────────
3769
+ handleKeyDown(e) {
3770
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.isComposing) {
3771
+ const l = e.key.toLowerCase();
3772
+ let i = null;
3773
+ if (!e.shiftKey && l === "b" ? i = "bold" : !e.shiftKey && l === "i" ? i = "italic" : !e.shiftKey && l === "u" ? i = "underline" : e.shiftKey && l === "x" && (i = "strike"), i) {
3774
+ e.preventDefault(), this.applyMark(i);
3775
+ return;
3776
+ }
3777
+ }
3778
+ if (e.key !== "Enter" || e.shiftKey || e.isComposing) return;
3779
+ const t = document.getSelection();
3780
+ if (!t || t.rangeCount === 0 || !t.isCollapsed) return;
3781
+ const o = this.getActiveBlock();
3782
+ o && (o.kind === "blockquote" || o.kind === "codeBlock") && this.exitBlockOnEnter(e, o.element);
3783
+ }
3784
+ /**
3785
+ * 활성 블록 뒤에 새 빈 단락을 만들고 캐럿을 그 안에 둔다.
3786
+ * 인용구 / 코드 블록의 Enter 탈출 동작에 사용.
3787
+ */
3788
+ exitBlockOnEnter(e, t) {
3789
+ e.preventDefault();
3790
+ const o = document.createElement("p");
3791
+ o.appendChild(document.createElement("br")), t.insertAdjacentElement("afterend", o), O(o), this.flush();
3792
+ }
3793
+ // ─── 드래그 앤 드롭 ──────────────────────────────────────────────────
3794
+ syncBlockHandles(e) {
3795
+ const t = /* @__PURE__ */ new Set();
3796
+ for (const o of e) {
3797
+ t.add(o.id);
3798
+ let l = this.handles.get(o.id);
3799
+ l || (l = this.createBlockHandle(o.id), this.handlesLayer.appendChild(l), this.handles.set(o.id, l)), this.positionHandle(l, o.element);
3800
+ }
3801
+ for (const [o, l] of this.handles)
3802
+ t.has(o) || (l.remove(), this.handles.delete(o));
3803
+ }
3804
+ repositionHandles() {
3805
+ if (this.destroyed) return;
3806
+ for (const o of this.state.blocks) {
3807
+ const l = this.handles.get(o.id);
3808
+ l && this.positionHandle(l, o.element);
3809
+ }
3810
+ const e = this.state.activeBlockId, t = e ? this.state.blocks.find((o) => o.id === e) : null;
3811
+ this.syncBlockLabelPill(t ?? null);
3812
+ }
3813
+ positionHandle(e, t) {
3814
+ const o = t.getBoundingClientRect(), l = this.contentWrap.getBoundingClientRect(), i = 22, r = o.height < i ? (o.height - i) / 2 : 0, s = o.top - l.top + this.contentWrap.scrollTop + r;
3815
+ e.style.top = `${s}px`;
3816
+ }
3817
+ createBlockHandle(e) {
3818
+ const t = document.createElement("div");
3819
+ t.className = "aw-block-controls", t.dataset.blockId = e;
3820
+ const o = document.createElement("div");
3821
+ o.className = "aw-block-handle", o.draggable = !0, o.setAttribute("role", "button"), o.setAttribute("aria-label", "블록 이동"), o.title = "드래그로 블록 이동", o.innerHTML = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><circle cx="6" cy="3" r="1.4"/><circle cx="10" cy="3" r="1.4"/><circle cx="6" cy="8" r="1.4"/><circle cx="10" cy="8" r="1.4"/><circle cx="6" cy="13" r="1.4"/><circle cx="10" cy="13" r="1.4"/></svg>', o.addEventListener("dragstart", (i) => {
3822
+ if (!i.dataTransfer) return;
3823
+ i.dataTransfer.setData(le, e), i.dataTransfer.setData("text/plain", e), i.dataTransfer.effectAllowed = "move", o.classList.add("is-dragging");
3824
+ const r = this.content.querySelector(
3825
+ `[data-block-id="${e}"]`
3826
+ );
3827
+ if (r) {
3828
+ r.classList.add("is-block-dragging");
3829
+ try {
3830
+ i.dataTransfer.setDragImage(r, 12, 12);
3831
+ } catch {
3832
+ }
3833
+ }
3834
+ }), o.addEventListener("dragend", () => {
3835
+ o.classList.remove("is-dragging");
3836
+ const i = this.content.querySelector(
3837
+ `[data-block-id="${e}"]`
3838
+ );
3839
+ i && i.classList.remove("is-block-dragging"), this.hideDropIndicator();
3840
+ });
3841
+ const l = document.createElement("button");
3842
+ return l.type = "button", l.className = "aw-block-add", l.title = "아래에 단락 추가", l.setAttribute("aria-label", "아래에 단락 추가"), l.innerHTML = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><line x1="8" y1="3" x2="8" y2="13"/><line x1="3" y1="8" x2="13" y2="8"/></svg>', l.addEventListener("mousedown", (i) => i.preventDefault()), l.addEventListener("click", (i) => {
3843
+ i.stopPropagation(), this.insertParagraphAfter(e);
3844
+ }), t.appendChild(o), t.appendChild(l), t;
3845
+ }
3846
+ /**
3847
+ * `blockId` 의 블록 뒤에 빈 단락을 삽입하고 캐럿을 그 단락으로 이동.
3848
+ * MutationObserver flush 가 새 단락에 신규 data-block-id 를 발급한다.
3849
+ */
3850
+ insertParagraphAfter(e) {
3851
+ const t = this.content.querySelector(
3852
+ `[data-block-id="${e}"]`
3853
+ );
3854
+ if (!t) return;
3855
+ const o = document.createElement("p");
3856
+ o.appendChild(document.createElement("br")), t.insertAdjacentElement("afterend", o), this.flush(), this.content.focus(), O(o);
3857
+ }
3858
+ handleDragOver(e) {
3859
+ if (!e.dataTransfer || !Array.from(e.dataTransfer.types).includes(le)) return;
3860
+ e.preventDefault(), e.dataTransfer.dropEffect = "move";
3861
+ const t = this.findDropTarget(e.clientY);
3862
+ t && this.showDropIndicator(t);
3863
+ }
3864
+ handleDrop(e) {
3865
+ if (!e.dataTransfer) return;
3866
+ const t = e.dataTransfer.getData(le);
3867
+ if (!t) return;
3868
+ e.preventDefault();
3869
+ const o = this.findDropTarget(e.clientY);
3870
+ if (this.hideDropIndicator(), !o || o.id === t) return;
3871
+ const l = this.content.querySelector(
3872
+ `[data-block-id="${t}"]`
3873
+ ), i = this.content.querySelector(
3874
+ `[data-block-id="${o.id}"]`
3875
+ );
3876
+ !l || !i || (o.before ? this.content.insertBefore(l, i) : this.content.insertBefore(l, i.nextSibling));
3877
+ }
3878
+ handleDragLeave(e) {
3879
+ this.content.contains(e.relatedTarget) || this.hideDropIndicator();
3880
+ }
3881
+ findDropTarget(e) {
3882
+ for (const o of this.state.blocks) {
3883
+ const l = o.element.getBoundingClientRect(), i = l.top + l.height / 2;
3884
+ if (e < i)
3885
+ return { id: o.id, before: !0 };
3886
+ }
3887
+ const t = this.state.blocks[this.state.blocks.length - 1];
3888
+ return t ? { id: t.id, before: !1 } : null;
3889
+ }
3890
+ showDropIndicator(e) {
3891
+ const t = this.content.querySelector(
3892
+ `[data-block-id="${e.id}"]`
3893
+ );
3894
+ if (!t) return;
3895
+ const o = t.getBoundingClientRect(), l = this.contentWrap.getBoundingClientRect(), i = (e.before ? o.top : o.bottom) - l.top + this.contentWrap.scrollTop;
3896
+ this.dropIndicator.style.top = `${i}px`, this.dropIndicator.style.display = "block";
3897
+ }
3898
+ hideDropIndicator() {
3899
+ this.dropIndicator.style.display = "none";
3900
+ }
3901
+ /**
3902
+ * 본문 블록들에 data-block-kind / data-block-label 을 갱신하고,
3903
+ * 활성 블록에 data-block-focused 를 표기한다. CSS 가 이 속성을 읽어
3904
+ * 포커스 인디케이터와 라벨 태그를 그린다.
3905
+ *
3906
+ * 자기 쓰기로 발생한 attributes 레코드는 takeRecords 로 흡수.
3907
+ */
3908
+ /**
3909
+ * 활성 블록 위쪽에 떠 있는 인터랙티브 라벨 pill 갱신.
3910
+ * - 활성 블록이 없으면 숨김
3911
+ * - 활성 블록의 라벨 텍스트로 갱신, 위치를 블록 상단에 맞춤
3912
+ * - 클릭 시 openBlockTypeDropdown 가 열려 블록 유형을 즉시 변경 가능
3913
+ */
3914
+ syncBlockLabelPill(e) {
3915
+ if (!e) {
3916
+ this.blockLabelPill.style.display = "none";
3917
+ return;
3918
+ }
3919
+ const { type: t, match: o } = U(e.element), l = se(e.element, t, o);
3920
+ this.blockLabelPill.textContent = l, this.blockLabelPill.style.display = "inline-flex";
3921
+ const i = e.element.getBoundingClientRect(), r = this.contentWrap.getBoundingClientRect(), s = Math.max(
3922
+ 0,
3923
+ i.top - r.top + this.contentWrap.scrollTop - 12
3924
+ ), c = i.left - r.left + 4;
3925
+ this.blockLabelPill.style.top = `${s}px`, this.blockLabelPill.style.left = `${c}px`;
3926
+ }
3927
+ /**
3928
+ * 하단 Status Bar 갱신.
3929
+ * - 좌측: 활성 블록 라벨 + id (없으면 — )
3930
+ * - 우측: 총 블록 수, 본문 글자 수(공백 제외)
3931
+ */
3932
+ syncStatusBar(e, t) {
3933
+ if (e) {
3934
+ const { type: r, match: s } = U(e.element);
3935
+ let c = se(e.element, r, s);
3936
+ if (e.kind === "codeBlock") {
3937
+ const a = e.element.querySelector(":scope > code"), d = a == null ? void 0 : a.getAttribute("data-language");
3938
+ d && (c += ` · ${d}`);
3939
+ }
3940
+ this.statusBlockEl.textContent = c;
3941
+ } else
3942
+ this.statusBlockEl.textContent = "— 선택된 블록 없음";
3943
+ this.statusBlocksEl.textContent = `${t.length} 블록`;
3944
+ const o = (this.content.textContent ?? "").replace(/\s+/g, ""), l = Array.from(o).length;
3945
+ this.statusCharsEl.textContent = `텍스트 ${l} 자`;
3946
+ const i = this.content.innerHTML.length;
3947
+ this.statusSourceEl.textContent = `소스 ${Sn(i)}`;
3948
+ }
3949
+ syncBlockMarkers(e) {
3950
+ for (const t of Array.from(this.content.children)) {
3951
+ if (!(t instanceof HTMLElement)) continue;
3952
+ const { type: o, match: l } = U(t);
3953
+ t.setAttribute("data-block-kind", l.kind), t.setAttribute("data-block-label", se(t, o, l));
3954
+ const i = this.blockIds.get(t);
3955
+ i && i === e ? t.setAttribute("data-block-focused", "") : t.hasAttribute("data-block-focused") && t.removeAttribute("data-block-focused");
3956
+ }
3957
+ this.observer.takeRecords();
3958
+ }
3959
+ applyToolbarState(e, t) {
3960
+ if (this.toolbar) {
3961
+ this.toolbar.setActive("bold", e.inline.bold), this.toolbar.setActive("italic", e.inline.italic), this.toolbar.setActive("underline", e.inline.underline), this.toolbar.setActive("strike", e.inline.strike);
3962
+ for (const o of Object.keys(xe)) {
3963
+ const l = xe[o];
3964
+ this.toolbar.setActive(o, (t == null ? void 0 : t.kind) === l);
3965
+ }
3966
+ this.toolbar.setVisible("bold", _(t, "bold")), this.toolbar.setVisible("italic", _(t, "italic")), this.toolbar.setVisible("underline", _(t, "underline")), this.toolbar.setVisible("strike", _(t, "strike")), this.toolbar.setVisible("fontFamily", Ee(t)), this.toolbar.setVisible("fontSize", Le(t)), this.toolbar.setVisible("color", Y(t)), this.toolbar.setVisible("backgroundColor", Y(t)), this.toolbar.setVisible("align", ie(t)), this.toolbar.setVisible("cellBg", V(t)), this.toolbar.setVisible("tableMenu", V(t)), this.toolbar.setVisible("codeLang", (t == null ? void 0 : t.kind) === "codeBlock"), this.toolbar.syncGroupVisibility();
3967
+ }
3968
+ }
3969
+ }
3970
+ function _(n, e) {
3971
+ return n ? (H[n.kind] ?? H.unknown).allowedMarks.includes(e) : !1;
3972
+ }
3973
+ function Ee(n) {
3974
+ return n ? (H[n.kind] ?? H.unknown).allowsInlineFormat : !1;
3975
+ }
3976
+ const kn = /* @__PURE__ */ new Set([
3977
+ "paragraph",
3978
+ "bulletList",
3979
+ "orderedList",
3980
+ "blockquote",
3981
+ "codeBlock",
3982
+ "table"
3983
+ ]);
3984
+ function Le(n) {
3985
+ return n ? kn.has(n.kind) : !1;
3986
+ }
3987
+ const vn = /* @__PURE__ */ new Set([
3988
+ "paragraph",
3989
+ "bulletList",
3990
+ "orderedList",
3991
+ "table"
3992
+ ]);
3993
+ function Y(n) {
3994
+ return n ? vn.has(n.kind) : !1;
3995
+ }
3996
+ const Cn = /* @__PURE__ */ new Set([
3997
+ "paragraph",
3998
+ "heading",
3999
+ "blockquote",
4000
+ "table"
4001
+ ]);
4002
+ function ie(n) {
4003
+ return n ? Cn.has(n.kind) : !1;
4004
+ }
4005
+ function V(n) {
4006
+ return (n == null ? void 0 : n.kind) === "table";
4007
+ }
4008
+ function xn(n) {
4009
+ const e = document.getSelection();
4010
+ if (!e || e.rangeCount === 0) return null;
4011
+ const t = e.getRangeAt(0);
4012
+ if (!n.contains(t.commonAncestorContainer)) return null;
4013
+ const o = document.createRange();
4014
+ o.selectNodeContents(n), o.setEnd(t.startContainer, t.startOffset);
4015
+ const l = o.toString().length;
4016
+ o.setEnd(t.endContainer, t.endOffset);
4017
+ const i = o.toString().length;
4018
+ return { start: l, end: i };
4019
+ }
4020
+ function En(n, e) {
4021
+ var u;
4022
+ const t = document.createTreeWalker(n, NodeFilter.SHOW_TEXT);
4023
+ let o = 0, l = null, i = 0, r = null, s = 0, c = t.nextNode();
4024
+ for (; c; ) {
4025
+ const m = ((u = c.textContent) == null ? void 0 : u.length) ?? 0;
4026
+ if (!l && o + m >= e.start && (l = c, i = e.start - o), !r && o + m >= e.end) {
4027
+ r = c, s = e.end - o;
4028
+ break;
4029
+ }
4030
+ o += m, c = t.nextNode();
4031
+ }
4032
+ if (!l || !r) return;
4033
+ const a = document.createRange();
4034
+ a.setStart(l, i), a.setEnd(r, s);
4035
+ const d = document.getSelection();
4036
+ d == null || d.removeAllRanges(), d == null || d.addRange(a);
4037
+ }
4038
+ function G(n) {
4039
+ let e = n;
4040
+ if (e.nodeType !== Node.ELEMENT_NODE && (e = e.parentElement), !(e instanceof Element)) return null;
4041
+ const t = e.closest("td, th");
4042
+ return t instanceof HTMLTableCellElement ? t : null;
4043
+ }
4044
+ function re(n, e, t) {
4045
+ const o = e.parentElement, l = t.parentElement;
4046
+ if (!(o instanceof HTMLTableRowElement) || !(l instanceof HTMLTableRowElement))
4047
+ return [e];
4048
+ const i = Array.from(n.querySelectorAll("tr")), r = i.indexOf(o), s = i.indexOf(l);
4049
+ if (r < 0 || s < 0) return [e];
4050
+ const c = Math.min(r, s), a = Math.max(r, s), d = Array.from(o.children).indexOf(e), u = Array.from(l.children).indexOf(t);
4051
+ if (d < 0 || u < 0) return [e];
4052
+ const m = Math.min(d, u), f = Math.max(d, u), p = [];
4053
+ for (let h = c; h <= a; h++) {
4054
+ const b = i[h];
4055
+ if (b)
4056
+ for (let v = m; v <= f; v++) {
4057
+ const k = b.children[v];
4058
+ k instanceof HTMLTableCellElement && p.push(k);
4059
+ }
4060
+ }
4061
+ return p;
4062
+ }
4063
+ function Ae(n, e, t) {
4064
+ const o = n.getBoundingClientRect(), l = 6;
4065
+ return e < o.left - l || e > o.right + l || t < o.top - l || t > o.bottom + l ? null : e >= o.right - l ? "right" : t >= o.bottom - l ? "bottom" : null;
4066
+ }
4067
+ function se(n, e, t) {
4068
+ return n.getAttribute("data-block-explicit") === "true" || !e.isEmpty(n) ? t.kind === "unknown" ? n.tagName.toLowerCase() : an(t) : "?";
4069
+ }
4070
+ function Ln(n) {
4071
+ const e = n.trim();
4072
+ if (!e) return null;
4073
+ let t;
4074
+ try {
4075
+ t = new URL(e);
4076
+ } catch {
4077
+ return /^[A-Za-z0-9_-]{11}$/.test(e) ? e : null;
4078
+ }
4079
+ const o = t.hostname.replace(/^www\./, "");
4080
+ if (o === "youtu.be")
4081
+ return t.pathname.slice(1).split("/")[0] || null;
4082
+ if (o === "youtube.com" || o === "m.youtube.com" || o === "youtube-nocookie.com") {
4083
+ if (t.pathname === "/watch") return t.searchParams.get("v");
4084
+ if (t.pathname.startsWith("/embed/") || t.pathname.startsWith("/shorts/") || t.pathname.startsWith("/v/")) return t.pathname.split("/")[2] ?? null;
4085
+ }
4086
+ return null;
4087
+ }
4088
+ function An(n) {
4089
+ var e;
4090
+ try {
4091
+ const l = new URL(n, "http://localhost").pathname.split("/").filter(Boolean).pop();
4092
+ return l ? decodeURIComponent(l) : "";
4093
+ } catch {
4094
+ return (((e = n.split("?")[0]) == null ? void 0 : e.split("#")[0]) ?? "").split("/").filter(Boolean).pop() ?? "";
4095
+ }
4096
+ }
4097
+ function Sn(n) {
4098
+ return n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : `${n}`;
4099
+ }
4100
+ function Se(n, e, t) {
4101
+ const o = document.createElement("button");
4102
+ return o.type = "button", o.className = "aw-status-action", o.title = n, o.setAttribute("aria-label", n), o.innerHTML = e, o.addEventListener("mousedown", (l) => l.preventDefault()), o.addEventListener("click", t), o;
4103
+ }
4104
+ function Tn() {
4105
+ return {
4106
+ blocks: [],
4107
+ activeBlockId: null,
4108
+ inline: { bold: !1, italic: !1, underline: !1, strike: !1 }
4109
+ };
4110
+ }
4111
+ function Z(n) {
4112
+ try {
4113
+ return document.queryCommandState(n);
4114
+ } catch {
4115
+ return !1;
4116
+ }
4117
+ }
4118
+ const Nn = /* @__PURE__ */ new Set([
4119
+ "P",
4120
+ "DIV",
4121
+ "SECTION",
4122
+ "ARTICLE",
4123
+ "HEADER",
4124
+ "FOOTER",
4125
+ "NAV",
4126
+ "ASIDE",
4127
+ "MAIN",
4128
+ "H1",
4129
+ "H2",
4130
+ "H3",
4131
+ "H4",
4132
+ "H5",
4133
+ "H6",
4134
+ "UL",
4135
+ "OL",
4136
+ "LI",
4137
+ "DL",
4138
+ "DT",
4139
+ "DD",
4140
+ "BLOCKQUOTE",
4141
+ "PRE",
4142
+ "HR",
4143
+ "TABLE",
4144
+ "FIGURE",
4145
+ "FIGCAPTION",
4146
+ "FORM",
4147
+ "FIELDSET",
4148
+ "ADDRESS"
4149
+ ]);
4150
+ function Mn(n) {
4151
+ return Nn.has(n.toUpperCase());
4152
+ }
4153
+ class Rn {
4154
+ constructor(e) {
4155
+ w(this, "root");
4156
+ w(this, "content");
4157
+ w(this, "blockIds", new De());
4158
+ w(this, "idFor", (e) => this.blockIds.ensure(e));
4159
+ w(this, "destroyed", !1);
4160
+ if (!(e.element instanceof HTMLElement))
4161
+ throw new TypeError("AerizViewer: options.element must be an HTMLElement");
4162
+ this.root = document.createElement("div"), this.root.className = "aeriz-wysiwyg aeriz-wysiwyg-viewer", e.theme && (this.root.dataset.theme = e.theme);
4163
+ const t = document.createElement("div");
4164
+ t.className = "aw-content-wrap", this.content = document.createElement("div"), this.content.className = "aw-content", this.content.contentEditable = "false", this.content.setAttribute("role", "document"), t.appendChild(this.content), this.root.appendChild(t), e.element.innerHTML = "", e.element.appendChild(this.root), e.initialBlocks && e.initialBlocks.length > 0 && this.setBlocks(e.initialBlocks);
4165
+ }
4166
+ /**
4167
+ * BlockSnapshot 배열로부터 본문을 다시 렌더한다.
4168
+ * 동일한 BlockType 레지스트리로 element 를 만들어 채우므로 외부 HTML 파싱
4169
+ * 경로를 거치지 않는다.
4170
+ */
4171
+ setBlocks(e) {
4172
+ un(this.content, e), ce(this.content, this.blockIds);
4173
+ }
4174
+ /** 현재 본문의 BlockSnapshot 배열을 반환한다. */
4175
+ getBlocks() {
4176
+ return Fe(this.listBlocksInternal());
4177
+ }
4178
+ /** 임의의 노드를 받아 그 노드가 속한 블록을 반환한다. 본문 밖이면 null. */
4179
+ getBlockOf(e) {
4180
+ return this.content.contains(e) ? ue(e, this.content, this.idFor) : null;
4181
+ }
4182
+ /**
4183
+ * 외부 컨텍스트(이메일, 다른 페이지의 HTML 영역 등) 에서도 동일한 시각으로
4184
+ * 보이도록 portable 인라인 스타일을 입힌 자급자족적 HTML 을 반환한다.
4185
+ */
4186
+ getHTML() {
4187
+ const e = this.content.cloneNode(!0), t = Pe(this.root), o = this.root.dataset.theme;
4188
+ return (o === "light" || o === "dark") && (t.mode = o), qe(e, t), Oe(e), e.innerHTML;
4189
+ }
4190
+ setTheme(e) {
4191
+ this.root.dataset.theme = e;
4192
+ }
4193
+ destroy() {
4194
+ this.destroyed || (this.destroyed = !0, this.root.remove());
4195
+ }
4196
+ listBlocksInternal() {
4197
+ return de(this.content, this.idFor);
4198
+ }
4199
+ }
4200
+ typeof globalThis.AerizWysiwyg == "object" && (globalThis.AerizEditor = globalThis.AerizWysiwyg.AerizEditor, globalThis.AerizViewer = globalThis.AerizWysiwyg.AerizViewer);
4201
+ export {
4202
+ Q as ALL_INLINE_MARKS,
4203
+ $n as AerizEditor,
4204
+ Rn as AerizViewer,
4205
+ F as BLOCK_ID_ATTR,
4206
+ He as BLOCK_TYPES,
4207
+ H as BLOCK_TYPE_BY_KIND,
4208
+ De as BlockIdRegistry,
4209
+ Ke as DEFAULT_TOOLBAR,
4210
+ qe as applyBlockInlineStyles,
4211
+ ce as assignMissingIds,
4212
+ U as classifyBlockElement,
4213
+ pn as extractInlineContent,
4214
+ hn as fillInlineContent,
4215
+ ue as findBlockFrom,
4216
+ an as getBlockLabel,
4217
+ q as isElementBlank,
4218
+ de as listBlocks,
4219
+ we as normalizeContent,
4220
+ O as placeCaretAtStart,
4221
+ Pe as readBlockStyleTheme,
4222
+ dn as renderBlock,
4223
+ un as renderBlocks,
4224
+ cn as serializeBlock,
4225
+ Fe as serializeBlocks,
4226
+ Oe as stripEditorOnlyAttrs
4227
+ };