@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.
- package/LICENSE +201 -0
- package/dist/aeriz-wysiwyg.css +1471 -0
- package/dist/aeriz-wysiwyg.esm.js +5602 -0
- package/dist/aeriz-wysiwyg.esm.min.js +4227 -0
- package/dist/aeriz-wysiwyg.js +5605 -0
- package/dist/aeriz-wysiwyg.min.css +1 -0
- package/dist/aeriz-wysiwyg.min.js +23 -0
- package/dist-types/core/AerizEditor.d.ts +337 -0
- package/dist-types/core/AerizEditor.d.ts.map +1 -0
- package/dist-types/core/AerizViewer.d.ts +49 -0
- package/dist-types/core/AerizViewer.d.ts.map +1 -0
- package/dist-types/core/blocks.d.ts +303 -0
- package/dist-types/core/blocks.d.ts.map +1 -0
- package/dist-types/core/fonts.d.ts +49 -0
- package/dist-types/core/fonts.d.ts.map +1 -0
- package/dist-types/core/highlight.d.ts +44 -0
- package/dist-types/core/highlight.d.ts.map +1 -0
- package/dist-types/core/i18n.d.ts +209 -0
- package/dist-types/core/i18n.d.ts.map +1 -0
- package/dist-types/core/import-html.d.ts +28 -0
- package/dist-types/core/import-html.d.ts.map +1 -0
- package/dist-types/core/import-markdown.d.ts +37 -0
- package/dist-types/core/import-markdown.d.ts.map +1 -0
- package/dist-types/index.d.ts +11 -0
- package/dist-types/index.d.ts.map +1 -0
- package/dist-types/types/index.d.ts +40 -0
- package/dist-types/types/index.d.ts.map +1 -0
- package/dist-types/ui/Toolbar.d.ts +21 -0
- package/dist-types/ui/Toolbar.d.ts.map +1 -0
- package/dist-types/ui/dialogs.d.ts +74 -0
- package/dist-types/ui/dialogs.d.ts.map +1 -0
- package/dist-types/ui/icons.d.ts +2 -0
- package/dist-types/ui/icons.d.ts.map +1 -0
- package/dist-types/ui/popovers.d.ts +70 -0
- package/dist-types/ui/popovers.d.ts.map +1 -0
- 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"><</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">></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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
+
/<(https?:\/\/[^\s&]+)>/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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2068
|
+
}
|
|
2069
|
+
function j(n) {
|
|
2070
|
+
return n.replace(/&/g, "&").replace(/"/g, """);
|
|
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
|
+
};
|