@teriusu/rich-editor 0.7.1 → 0.7.2
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/dist/components/BubbleToolbar.svelte +169 -118
- package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -1
- package/dist/components/FixedToolbar.svelte +410 -814
- package/dist/components/FixedToolbar.svelte.d.ts +0 -1
- package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
- package/dist/components/SlashCommandMenu.svelte +28 -25
- package/dist/components/SlashCommandMenu.svelte.d.ts +1 -1
- package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
- package/dist/components/TipTapEditor.svelte +2 -64
- package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
- package/dist/extensions/Columns.d.ts.map +1 -1
- package/dist/extensions/Columns.js +35 -0
- package/dist/extensions/FixedDetails.d.ts.map +1 -1
- package/dist/extensions/FixedDetails.js +11 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/styles/editor.css +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -7
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +0 -3
- package/dist/utils/sanitize.test.js +0 -7
- package/package.json +1 -2
- package/dist/extensions/VideoBlock.d.ts +0 -16
- package/dist/extensions/VideoBlock.d.ts.map +0 -1
- package/dist/extensions/VideoBlock.js +0 -87
- package/dist/extensions/VideoBlock.test.d.ts +0 -2
- package/dist/extensions/VideoBlock.test.d.ts.map +0 -1
- package/dist/extensions/VideoBlock.test.js +0 -54
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
Italic,
|
|
6
6
|
Underline as UnderlineIcon,
|
|
7
7
|
Strikethrough,
|
|
8
|
-
Highlighter,
|
|
9
|
-
LinkIcon,
|
|
10
8
|
AlignLeft,
|
|
11
9
|
AlignCenter,
|
|
12
10
|
AlignRight,
|
|
11
|
+
LinkIcon,
|
|
13
12
|
Heading1,
|
|
14
13
|
Heading2,
|
|
15
14
|
Heading3,
|
|
@@ -24,24 +23,14 @@
|
|
|
24
23
|
Undo,
|
|
25
24
|
Redo,
|
|
26
25
|
Table as TableIcon,
|
|
27
|
-
Rows3,
|
|
28
|
-
Columns3,
|
|
29
|
-
Trash2,
|
|
30
|
-
SplitSquareHorizontal,
|
|
31
|
-
Combine,
|
|
32
|
-
PanelTop,
|
|
33
|
-
PanelLeft,
|
|
34
|
-
Paintbrush,
|
|
35
26
|
CheckSquare,
|
|
36
|
-
Superscript,
|
|
37
|
-
Subscript,
|
|
38
|
-
Video,
|
|
39
27
|
ChevronRight,
|
|
40
28
|
Paperclip,
|
|
41
|
-
Film,
|
|
42
29
|
Columns2,
|
|
30
|
+
Columns3,
|
|
43
31
|
Tv,
|
|
44
|
-
|
|
32
|
+
Plus,
|
|
33
|
+
Pilcrow,
|
|
45
34
|
} from "lucide-svelte";
|
|
46
35
|
import { cn } from "../utils/cn";
|
|
47
36
|
import InputModal from "./InputModal.svelte";
|
|
@@ -52,7 +41,6 @@
|
|
|
52
41
|
features,
|
|
53
42
|
onPdfClick,
|
|
54
43
|
onFileClick,
|
|
55
|
-
onVideoClick,
|
|
56
44
|
onPromptLink,
|
|
57
45
|
onPromptImage,
|
|
58
46
|
onPromptMbus,
|
|
@@ -61,7 +49,6 @@
|
|
|
61
49
|
features: Set<ToolbarFeature>;
|
|
62
50
|
onPdfClick: () => void;
|
|
63
51
|
onFileClick?: () => void;
|
|
64
|
-
onVideoClick?: () => void;
|
|
65
52
|
onPromptLink?: PromptHandler;
|
|
66
53
|
onPromptImage?: PromptHandler;
|
|
67
54
|
onPromptMbus?: PromptHandler;
|
|
@@ -71,46 +58,24 @@
|
|
|
71
58
|
|
|
72
59
|
const iconSize = 16;
|
|
73
60
|
|
|
74
|
-
let
|
|
75
|
-
let
|
|
76
|
-
let
|
|
77
|
-
let
|
|
78
|
-
let
|
|
79
|
-
let codeMenuEl: HTMLDivElement | undefined = $state();
|
|
80
|
-
let tableMenuEl: HTMLDivElement | undefined = $state();
|
|
81
|
-
let colorMenuEl: HTMLDivElement | undefined = $state();
|
|
82
|
-
|
|
83
|
-
const TEXT_COLORS = [
|
|
84
|
-
{ label: "기본", value: "" },
|
|
85
|
-
{ label: "검정", value: "#000000" },
|
|
86
|
-
{ label: "회색", value: "#6b7280" },
|
|
87
|
-
{ label: "빨강", value: "#dc2626" },
|
|
88
|
-
{ label: "주황", value: "#ea580c" },
|
|
89
|
-
{ label: "노랑", value: "#ca8a04" },
|
|
90
|
-
{ label: "초록", value: "#16a34a" },
|
|
91
|
-
{ label: "파랑", value: "#2563eb" },
|
|
92
|
-
{ label: "보라", value: "#7c3aed" }
|
|
93
|
-
];
|
|
61
|
+
let blockMenuOpen = $state(false);
|
|
62
|
+
let insertMenuOpen = $state(false);
|
|
63
|
+
let modalState: { type: "link" | "image" | "mbus" } | null = $state(null);
|
|
64
|
+
let blockMenuEl: HTMLDivElement | undefined = $state();
|
|
65
|
+
let insertMenuEl: HTMLDivElement | undefined = $state();
|
|
94
66
|
|
|
95
67
|
$effect(() => {
|
|
96
|
-
if (!
|
|
68
|
+
if (!blockMenuOpen && !insertMenuOpen) return;
|
|
97
69
|
function handleClick(e: MouseEvent) {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
colorMenuEl &&
|
|
104
|
-
!colorMenuEl.contains(e.target as Node) &&
|
|
105
|
-
!colorPickerActive
|
|
106
|
-
)
|
|
107
|
-
colorMenuOpen = false;
|
|
70
|
+
if (blockMenuEl && !blockMenuEl.contains(e.target as Node))
|
|
71
|
+
blockMenuOpen = false;
|
|
72
|
+
if (insertMenuEl && !insertMenuEl.contains(e.target as Node))
|
|
73
|
+
insertMenuOpen = false;
|
|
108
74
|
}
|
|
109
75
|
function handleKeyDown(e: KeyboardEvent) {
|
|
110
76
|
if (e.key === "Escape") {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
colorMenuOpen = false;
|
|
77
|
+
blockMenuOpen = false;
|
|
78
|
+
insertMenuOpen = false;
|
|
114
79
|
}
|
|
115
80
|
}
|
|
116
81
|
document.addEventListener("mousedown", handleClick);
|
|
@@ -122,6 +87,7 @@
|
|
|
122
87
|
});
|
|
123
88
|
|
|
124
89
|
async function addLink() {
|
|
90
|
+
insertMenuOpen = false;
|
|
125
91
|
if (onPromptLink) {
|
|
126
92
|
const previous = editor.isActive("link")
|
|
127
93
|
? (editor.getAttributes("link").href as string) || ""
|
|
@@ -144,6 +110,7 @@
|
|
|
144
110
|
}
|
|
145
111
|
|
|
146
112
|
async function addImage() {
|
|
113
|
+
insertMenuOpen = false;
|
|
147
114
|
if (onPromptImage) {
|
|
148
115
|
const url = await onPromptImage("");
|
|
149
116
|
if (!url) return;
|
|
@@ -153,851 +120,521 @@
|
|
|
153
120
|
modalState = { type: "image" };
|
|
154
121
|
}
|
|
155
122
|
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
123
|
+
async function addMbus() {
|
|
124
|
+
insertMenuOpen = false;
|
|
125
|
+
if (onPromptMbus) {
|
|
126
|
+
const url = await onPromptMbus("");
|
|
127
|
+
if (!url) return;
|
|
128
|
+
editor.chain().focus().setMbusVideo({ src: url }).run();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
modalState = { type: "mbus" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const currentBlockLabel = $derived.by(() => {
|
|
135
|
+
if (editor.isActive("heading", { level: 1 })) return "제목 1";
|
|
136
|
+
if (editor.isActive("heading", { level: 2 })) return "제목 2";
|
|
137
|
+
if (editor.isActive("heading", { level: 3 })) return "제목 3";
|
|
138
|
+
if (editor.isActive("bulletList")) return "글머리 목록";
|
|
139
|
+
if (editor.isActive("orderedList")) return "번호 목록";
|
|
140
|
+
if (editor.isActive("taskList")) return "체크리스트";
|
|
141
|
+
if (editor.isActive("blockquote")) return "인용문";
|
|
142
|
+
if (editor.isActive("details")) return "토글";
|
|
143
|
+
return "본문";
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
function runBlock(fn: () => void) {
|
|
147
|
+
fn();
|
|
148
|
+
blockMenuOpen = false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runInsert(fn: () => void) {
|
|
152
|
+
fn();
|
|
153
|
+
insertMenuOpen = false;
|
|
159
154
|
}
|
|
160
155
|
|
|
161
|
-
const
|
|
156
|
+
const hasInsertItems = $derived(
|
|
157
|
+
has("image") ||
|
|
158
|
+
has("link") ||
|
|
159
|
+
has("pdf") ||
|
|
160
|
+
has("file") ||
|
|
161
|
+
has("mbus") ||
|
|
162
|
+
has("table") ||
|
|
163
|
+
has("columns-2") ||
|
|
164
|
+
has("columns-3") ||
|
|
165
|
+
has("horizontal-rule") ||
|
|
166
|
+
has("code-block"),
|
|
167
|
+
);
|
|
162
168
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
];
|
|
169
|
+
const hasBlockItems = $derived(
|
|
170
|
+
has("h1") ||
|
|
171
|
+
has("h2") ||
|
|
172
|
+
has("h3") ||
|
|
173
|
+
has("bullet-list") ||
|
|
174
|
+
has("ordered-list") ||
|
|
175
|
+
has("checklist") ||
|
|
176
|
+
has("blockquote") ||
|
|
177
|
+
has("toggle"),
|
|
178
|
+
);
|
|
174
179
|
</script>
|
|
175
180
|
|
|
176
181
|
<div
|
|
177
182
|
class="hce-toolbar sticky z-30 flex flex-wrap items-center gap-1.5 px-3 py-2 border-b border-border bg-background rounded-t-xl"
|
|
178
183
|
style="top: var(--header-height, 74px)"
|
|
179
184
|
>
|
|
180
|
-
|
|
185
|
+
{#if has('undo') || has('redo')}
|
|
186
|
+
<!-- Undo / Redo -->
|
|
181
187
|
<div class="hce-toolbar-group">
|
|
182
|
-
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
onclick={() => editor.chain().focus().toggleBold().run()}
|
|
186
|
-
data-tooltip="굵게"
|
|
187
|
-
aria-label="굵게"
|
|
188
|
-
class={cn(
|
|
189
|
-
"p-1.5 rounded-md transition-colors",
|
|
190
|
-
editor.isActive("bold")
|
|
191
|
-
? "bg-primary/10 text-primary"
|
|
192
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
193
|
-
)}
|
|
194
|
-
>
|
|
195
|
-
<Bold size={iconSize} />
|
|
196
|
-
</button>
|
|
197
|
-
{/if}
|
|
198
|
-
{#if has('italic')}
|
|
199
|
-
<button
|
|
200
|
-
type="button"
|
|
201
|
-
onclick={() => editor.chain().focus().toggleItalic().run()}
|
|
202
|
-
data-tooltip="기울임"
|
|
203
|
-
aria-label="기울임"
|
|
204
|
-
class={cn(
|
|
205
|
-
"p-1.5 rounded-md transition-colors",
|
|
206
|
-
editor.isActive("italic")
|
|
207
|
-
? "bg-primary/10 text-primary"
|
|
208
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
209
|
-
)}
|
|
210
|
-
>
|
|
211
|
-
<Italic size={iconSize} />
|
|
212
|
-
</button>
|
|
213
|
-
{/if}
|
|
214
|
-
{#if has('underline')}
|
|
215
|
-
<button
|
|
216
|
-
type="button"
|
|
217
|
-
onclick={() => editor.chain().focus().toggleUnderline().run()}
|
|
218
|
-
data-tooltip="밑줄"
|
|
219
|
-
aria-label="밑줄"
|
|
220
|
-
class={cn(
|
|
221
|
-
"p-1.5 rounded-md transition-colors",
|
|
222
|
-
editor.isActive("underline")
|
|
223
|
-
? "bg-primary/10 text-primary"
|
|
224
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
225
|
-
)}
|
|
226
|
-
>
|
|
227
|
-
<UnderlineIcon size={iconSize} />
|
|
228
|
-
</button>
|
|
229
|
-
{/if}
|
|
230
|
-
{#if has('strike')}
|
|
231
|
-
<button
|
|
232
|
-
type="button"
|
|
233
|
-
onclick={() => editor.chain().focus().toggleStrike().run()}
|
|
234
|
-
data-tooltip="취소선"
|
|
235
|
-
aria-label="취소선"
|
|
236
|
-
class={cn(
|
|
237
|
-
"p-1.5 rounded-md transition-colors",
|
|
238
|
-
editor.isActive("strike")
|
|
239
|
-
? "bg-primary/10 text-primary"
|
|
240
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
241
|
-
)}
|
|
242
|
-
>
|
|
243
|
-
<Strikethrough size={iconSize} />
|
|
244
|
-
</button>
|
|
245
|
-
{/if}
|
|
246
|
-
{#if has('highlight')}
|
|
247
|
-
<button
|
|
248
|
-
type="button"
|
|
249
|
-
onclick={() => editor.chain().focus().toggleHighlight().run()}
|
|
250
|
-
data-tooltip="하이라이트"
|
|
251
|
-
aria-label="하이라이트"
|
|
252
|
-
class={cn(
|
|
253
|
-
"p-1.5 rounded-md transition-colors",
|
|
254
|
-
editor.isActive("highlight")
|
|
255
|
-
? "bg-primary/10 text-primary"
|
|
256
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
257
|
-
)}
|
|
258
|
-
>
|
|
259
|
-
<Highlighter size={iconSize} />
|
|
260
|
-
</button>
|
|
261
|
-
{/if}
|
|
262
|
-
{#if has('superscript')}
|
|
263
|
-
<button
|
|
264
|
-
type="button"
|
|
265
|
-
onclick={() => editor.chain().focus().toggleSuperscript().run()}
|
|
266
|
-
data-tooltip="위첨자"
|
|
267
|
-
aria-label="위첨자"
|
|
268
|
-
class={cn(
|
|
269
|
-
"p-1.5 rounded-md transition-colors",
|
|
270
|
-
editor.isActive("superscript")
|
|
271
|
-
? "bg-primary/10 text-primary"
|
|
272
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
273
|
-
)}
|
|
274
|
-
>
|
|
275
|
-
<Superscript size={iconSize} />
|
|
276
|
-
</button>
|
|
277
|
-
{/if}
|
|
278
|
-
{#if has('subscript')}
|
|
279
|
-
<button
|
|
280
|
-
type="button"
|
|
281
|
-
onclick={() => editor.chain().focus().toggleSubscript().run()}
|
|
282
|
-
data-tooltip="아래첨자"
|
|
283
|
-
aria-label="아래첨자"
|
|
284
|
-
class={cn(
|
|
285
|
-
"p-1.5 rounded-md transition-colors",
|
|
286
|
-
editor.isActive("subscript")
|
|
287
|
-
? "bg-primary/10 text-primary"
|
|
288
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
289
|
-
)}
|
|
290
|
-
>
|
|
291
|
-
<Subscript size={iconSize} />
|
|
292
|
-
</button>
|
|
293
|
-
{/if}
|
|
294
|
-
{#if has('text-color')}
|
|
295
|
-
<div bind:this={colorMenuEl} class="relative">
|
|
188
|
+
{#if has('undo')}
|
|
296
189
|
<button
|
|
297
190
|
type="button"
|
|
298
|
-
onclick={() => (
|
|
299
|
-
|
|
300
|
-
|
|
191
|
+
onclick={() => editor.chain().focus().undo().run()}
|
|
192
|
+
disabled={!editor.can().undo()}
|
|
193
|
+
data-tooltip="실행 취소"
|
|
194
|
+
aria-label="실행 취소"
|
|
301
195
|
class={cn(
|
|
302
|
-
"
|
|
303
|
-
editor.
|
|
304
|
-
? "bg-primary/10 text-primary"
|
|
305
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
196
|
+
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
197
|
+
!editor.can().undo() && "opacity-30 pointer-events-none",
|
|
306
198
|
)}
|
|
307
199
|
>
|
|
308
|
-
<
|
|
309
|
-
<ChevronDown size={12} />
|
|
200
|
+
<Undo size={iconSize} />
|
|
310
201
|
</button>
|
|
311
|
-
{#if colorMenuOpen}
|
|
312
|
-
<div
|
|
313
|
-
class="absolute top-full left-0 mt-1 bg-popover border border-border rounded-lg shadow-lg z-50 p-2"
|
|
314
|
-
style="min-width: 180px"
|
|
315
|
-
>
|
|
316
|
-
<div class="grid grid-cols-3 gap-1.5">
|
|
317
|
-
{#each TEXT_COLORS as c}
|
|
318
|
-
<button
|
|
319
|
-
type="button"
|
|
320
|
-
title={c.label}
|
|
321
|
-
class="h-8 rounded-md border border-border transition-transform hover:scale-105 flex items-center justify-center text-xs font-bold"
|
|
322
|
-
style="color: {c.value || '#000'}"
|
|
323
|
-
onclick={() => {
|
|
324
|
-
if (c.value) {
|
|
325
|
-
editor.chain().focus().setColor(c.value).run();
|
|
326
|
-
} else {
|
|
327
|
-
editor.chain().focus().unsetColor().run();
|
|
328
|
-
}
|
|
329
|
-
colorMenuOpen = false;
|
|
330
|
-
}}
|
|
331
|
-
>
|
|
332
|
-
{c.value ? "A" : "×"}
|
|
333
|
-
</button>
|
|
334
|
-
{/each}
|
|
335
|
-
</div>
|
|
336
|
-
<label
|
|
337
|
-
class="mt-2 flex items-center justify-between gap-2 px-1 text-xs text-muted-foreground cursor-pointer hover:text-foreground"
|
|
338
|
-
>
|
|
339
|
-
<span>직접 선택</span>
|
|
340
|
-
<input
|
|
341
|
-
type="color"
|
|
342
|
-
class="h-6 w-10 cursor-pointer rounded border border-border bg-transparent p-0"
|
|
343
|
-
value={(editor.getAttributes("textStyle").color as string) || "#000000"}
|
|
344
|
-
onpointerdown={() => (colorPickerActive = true)}
|
|
345
|
-
onclick={(e) => e.stopPropagation()}
|
|
346
|
-
oninput={(e) => {
|
|
347
|
-
const v = (e.target as HTMLInputElement).value;
|
|
348
|
-
editor.chain().focus().setColor(v).run();
|
|
349
|
-
}}
|
|
350
|
-
onchange={() => {
|
|
351
|
-
setTimeout(() => (colorPickerActive = false), 200);
|
|
352
|
-
}}
|
|
353
|
-
onblur={() => {
|
|
354
|
-
setTimeout(() => (colorPickerActive = false), 200);
|
|
355
|
-
}}
|
|
356
|
-
/>
|
|
357
|
-
</label>
|
|
358
|
-
</div>
|
|
359
202
|
{/if}
|
|
360
|
-
|
|
361
|
-
{/if}
|
|
362
|
-
|
|
363
|
-
</div>
|
|
364
|
-
|
|
365
|
-
{#if has('align-left') || has('align-center') || has('align-right')}
|
|
366
|
-
<!-- Alignment -->
|
|
367
|
-
<div class="hce-toolbar-group">
|
|
368
|
-
{#if has('align-left')}
|
|
369
|
-
<button
|
|
370
|
-
type="button"
|
|
371
|
-
onclick={() => editor.chain().focus().setTextAlign("left").run()}
|
|
372
|
-
data-tooltip="왼쪽 정렬"
|
|
373
|
-
aria-label="왼쪽 정렬"
|
|
374
|
-
class={cn(
|
|
375
|
-
"p-1.5 rounded-md transition-colors",
|
|
376
|
-
editor.isActive({ textAlign: "left" })
|
|
377
|
-
? "bg-primary/10 text-primary"
|
|
378
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
379
|
-
)}
|
|
380
|
-
>
|
|
381
|
-
<AlignLeft size={iconSize} />
|
|
382
|
-
</button>
|
|
383
|
-
{/if}
|
|
384
|
-
{#if has('align-center')}
|
|
385
|
-
<button
|
|
386
|
-
type="button"
|
|
387
|
-
onclick={() => editor.chain().focus().setTextAlign("center").run()}
|
|
388
|
-
data-tooltip="가운데 정렬"
|
|
389
|
-
aria-label="가운데 정렬"
|
|
390
|
-
class={cn(
|
|
391
|
-
"p-1.5 rounded-md transition-colors",
|
|
392
|
-
editor.isActive({ textAlign: "center" })
|
|
393
|
-
? "bg-primary/10 text-primary"
|
|
394
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
395
|
-
)}
|
|
396
|
-
>
|
|
397
|
-
<AlignCenter size={iconSize} />
|
|
398
|
-
</button>
|
|
399
|
-
{/if}
|
|
400
|
-
{#if has('align-right')}
|
|
401
|
-
<button
|
|
402
|
-
type="button"
|
|
403
|
-
onclick={() => editor.chain().focus().setTextAlign("right").run()}
|
|
404
|
-
data-tooltip="오른쪽 정렬"
|
|
405
|
-
aria-label="오른쪽 정렬"
|
|
406
|
-
class={cn(
|
|
407
|
-
"p-1.5 rounded-md transition-colors",
|
|
408
|
-
editor.isActive({ textAlign: "right" })
|
|
409
|
-
? "bg-primary/10 text-primary"
|
|
410
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
411
|
-
)}
|
|
412
|
-
>
|
|
413
|
-
<AlignRight size={iconSize} />
|
|
414
|
-
</button>
|
|
415
|
-
{/if}
|
|
416
|
-
|
|
417
|
-
</div>
|
|
418
|
-
{/if}
|
|
419
|
-
|
|
420
|
-
{#if has('h1') || has('h2') || has('h3')}
|
|
421
|
-
<!-- Headings -->
|
|
422
|
-
<div class="hce-toolbar-group">
|
|
423
|
-
{#if has('h1')}
|
|
424
|
-
<button
|
|
425
|
-
type="button"
|
|
426
|
-
onclick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
|
427
|
-
data-tooltip="제목 1"
|
|
428
|
-
aria-label="제목 1"
|
|
429
|
-
class={cn(
|
|
430
|
-
"p-1.5 rounded-md transition-colors",
|
|
431
|
-
editor.isActive("heading", { level: 1 })
|
|
432
|
-
? "bg-primary/10 text-primary"
|
|
433
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
434
|
-
)}
|
|
435
|
-
>
|
|
436
|
-
<Heading1 size={iconSize} />
|
|
437
|
-
</button>
|
|
438
|
-
{/if}
|
|
439
|
-
{#if has('h2')}
|
|
440
|
-
<button
|
|
441
|
-
type="button"
|
|
442
|
-
onclick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
|
443
|
-
data-tooltip="제목 2"
|
|
444
|
-
aria-label="제목 2"
|
|
445
|
-
class={cn(
|
|
446
|
-
"p-1.5 rounded-md transition-colors",
|
|
447
|
-
editor.isActive("heading", { level: 2 })
|
|
448
|
-
? "bg-primary/10 text-primary"
|
|
449
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
450
|
-
)}
|
|
451
|
-
>
|
|
452
|
-
<Heading2 size={iconSize} />
|
|
453
|
-
</button>
|
|
454
|
-
{/if}
|
|
455
|
-
{#if has('h3')}
|
|
456
|
-
<button
|
|
457
|
-
type="button"
|
|
458
|
-
onclick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
|
459
|
-
data-tooltip="제목 3"
|
|
460
|
-
aria-label="제목 3"
|
|
461
|
-
class={cn(
|
|
462
|
-
"p-1.5 rounded-md transition-colors",
|
|
463
|
-
editor.isActive("heading", { level: 3 })
|
|
464
|
-
? "bg-primary/10 text-primary"
|
|
465
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
466
|
-
)}
|
|
467
|
-
>
|
|
468
|
-
<Heading3 size={iconSize} />
|
|
469
|
-
</button>
|
|
470
|
-
{/if}
|
|
471
|
-
|
|
472
|
-
</div>
|
|
473
|
-
{/if}
|
|
474
|
-
|
|
475
|
-
{#if has('bullet-list') || has('ordered-list') || has('checklist') || has('blockquote') || has('horizontal-rule') || has('toggle')}
|
|
476
|
-
<!-- Lists & Blocks -->
|
|
477
|
-
<div class="hce-toolbar-group">
|
|
478
|
-
{#if has('bullet-list')}
|
|
479
|
-
<button
|
|
480
|
-
type="button"
|
|
481
|
-
onclick={() => editor.chain().focus().toggleBulletList().run()}
|
|
482
|
-
data-tooltip="글머리 목록"
|
|
483
|
-
aria-label="글머리 목록"
|
|
484
|
-
class={cn(
|
|
485
|
-
"p-1.5 rounded-md transition-colors",
|
|
486
|
-
editor.isActive("bulletList")
|
|
487
|
-
? "bg-primary/10 text-primary"
|
|
488
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
489
|
-
)}
|
|
490
|
-
>
|
|
491
|
-
<List size={iconSize} />
|
|
492
|
-
</button>
|
|
493
|
-
{/if}
|
|
494
|
-
{#if has('ordered-list')}
|
|
495
|
-
<button
|
|
496
|
-
type="button"
|
|
497
|
-
onclick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
498
|
-
data-tooltip="번호 목록"
|
|
499
|
-
aria-label="번호 목록"
|
|
500
|
-
class={cn(
|
|
501
|
-
"p-1.5 rounded-md transition-colors",
|
|
502
|
-
editor.isActive("orderedList")
|
|
503
|
-
? "bg-primary/10 text-primary"
|
|
504
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
505
|
-
)}
|
|
506
|
-
>
|
|
507
|
-
<ListOrdered size={iconSize} />
|
|
508
|
-
</button>
|
|
509
|
-
{/if}
|
|
510
|
-
{#if has('checklist')}
|
|
511
|
-
<button
|
|
512
|
-
type="button"
|
|
513
|
-
onclick={() => editor.chain().focus().toggleTaskList().run()}
|
|
514
|
-
data-tooltip="체크리스트"
|
|
515
|
-
aria-label="체크리스트"
|
|
516
|
-
class={cn(
|
|
517
|
-
"p-1.5 rounded-md transition-colors",
|
|
518
|
-
editor.isActive("taskList")
|
|
519
|
-
? "bg-primary/10 text-primary"
|
|
520
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
521
|
-
)}
|
|
522
|
-
>
|
|
523
|
-
<CheckSquare size={iconSize} />
|
|
524
|
-
</button>
|
|
525
|
-
{/if}
|
|
526
|
-
{#if has('blockquote')}
|
|
527
|
-
<button
|
|
528
|
-
type="button"
|
|
529
|
-
onclick={() => editor.chain().focus().toggleBlockquote().run()}
|
|
530
|
-
data-tooltip="인용문"
|
|
531
|
-
aria-label="인용문"
|
|
532
|
-
class={cn(
|
|
533
|
-
"p-1.5 rounded-md transition-colors",
|
|
534
|
-
editor.isActive("blockquote")
|
|
535
|
-
? "bg-primary/10 text-primary"
|
|
536
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
537
|
-
)}
|
|
538
|
-
>
|
|
539
|
-
<Quote size={iconSize} />
|
|
540
|
-
</button>
|
|
541
|
-
{/if}
|
|
542
|
-
{#if has('horizontal-rule')}
|
|
543
|
-
<button
|
|
544
|
-
type="button"
|
|
545
|
-
onclick={() => editor.chain().focus().setHorizontalRule().run()}
|
|
546
|
-
data-tooltip="구분선"
|
|
547
|
-
aria-label="구분선"
|
|
548
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
549
|
-
>
|
|
550
|
-
<Minus size={iconSize} />
|
|
551
|
-
</button>
|
|
552
|
-
{/if}
|
|
553
|
-
{#if has('toggle')}
|
|
554
|
-
<button
|
|
555
|
-
type="button"
|
|
556
|
-
onclick={() => editor.chain().focus().setDetails().run()}
|
|
557
|
-
data-tooltip="토글 (접기/펼치기)"
|
|
558
|
-
aria-label="토글 (접기/펼치기)"
|
|
559
|
-
class={cn(
|
|
560
|
-
"p-1.5 rounded-md transition-colors",
|
|
561
|
-
editor.isActive("details")
|
|
562
|
-
? "bg-primary/10 text-primary"
|
|
563
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
564
|
-
)}
|
|
565
|
-
>
|
|
566
|
-
<ChevronRight size={iconSize} />
|
|
567
|
-
</button>
|
|
568
|
-
{/if}
|
|
569
|
-
|
|
570
|
-
</div>
|
|
571
|
-
{/if}
|
|
572
|
-
|
|
573
|
-
{#if has('link') || has('image') || has('pdf') || has('youtube') || has('video') || has('file') || has('mbus') || has('columns-2') || has('columns-3') || has('table') || has('code-block')}
|
|
574
|
-
<!-- Media & Layout -->
|
|
575
|
-
<div class="hce-toolbar-group">
|
|
576
|
-
{#if has('link')}
|
|
577
|
-
<button
|
|
578
|
-
type="button"
|
|
579
|
-
onclick={addLink}
|
|
580
|
-
data-tooltip="링크"
|
|
581
|
-
aria-label="링크"
|
|
582
|
-
class={cn(
|
|
583
|
-
"p-1.5 rounded-md transition-colors",
|
|
584
|
-
editor.isActive("link")
|
|
585
|
-
? "bg-primary/10 text-primary"
|
|
586
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
587
|
-
)}
|
|
588
|
-
>
|
|
589
|
-
<LinkIcon size={iconSize} />
|
|
590
|
-
</button>
|
|
591
|
-
{/if}
|
|
592
|
-
{#if has('image')}
|
|
593
|
-
<button
|
|
594
|
-
type="button"
|
|
595
|
-
onclick={addImage}
|
|
596
|
-
data-tooltip="이미지"
|
|
597
|
-
aria-label="이미지"
|
|
598
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
599
|
-
>
|
|
600
|
-
<ImageIcon size={iconSize} />
|
|
601
|
-
</button>
|
|
602
|
-
{/if}
|
|
603
|
-
{#if has('pdf')}
|
|
604
|
-
<button
|
|
605
|
-
type="button"
|
|
606
|
-
onclick={onPdfClick}
|
|
607
|
-
data-tooltip="PDF 삽입"
|
|
608
|
-
aria-label="PDF 삽입"
|
|
609
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
610
|
-
>
|
|
611
|
-
<FileText size={iconSize} />
|
|
612
|
-
</button>
|
|
613
|
-
{/if}
|
|
614
|
-
{#if has('youtube')}
|
|
615
|
-
<button
|
|
616
|
-
type="button"
|
|
617
|
-
onclick={() => {
|
|
618
|
-
const url = window.prompt("YouTube URL을 입력하세요");
|
|
619
|
-
if (url) editor.chain().focus().setYoutubeVideo({ src: url }).run();
|
|
620
|
-
}}
|
|
621
|
-
data-tooltip="YouTube 영상"
|
|
622
|
-
aria-label="YouTube 영상"
|
|
623
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
624
|
-
>
|
|
625
|
-
<Video size={iconSize} />
|
|
626
|
-
</button>
|
|
627
|
-
{/if}
|
|
628
|
-
{#if has('mbus')}
|
|
629
|
-
<button
|
|
630
|
-
type="button"
|
|
631
|
-
onclick={async () => {
|
|
632
|
-
if (onPromptMbus) {
|
|
633
|
-
const url = await onPromptMbus("");
|
|
634
|
-
if (!url) return;
|
|
635
|
-
editor.chain().focus().setMbusVideo({ src: url }).run();
|
|
636
|
-
} else {
|
|
637
|
-
modalState = { type: "mbus" };
|
|
638
|
-
}
|
|
639
|
-
}}
|
|
640
|
-
data-tooltip="미디버스 영상"
|
|
641
|
-
aria-label="미디버스 영상"
|
|
642
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
643
|
-
>
|
|
644
|
-
<Tv size={iconSize} />
|
|
645
|
-
</button>
|
|
646
|
-
{/if}
|
|
647
|
-
{#if has('video') && onVideoClick}
|
|
648
|
-
<button
|
|
649
|
-
type="button"
|
|
650
|
-
onclick={onVideoClick}
|
|
651
|
-
data-tooltip="영상 파일"
|
|
652
|
-
aria-label="영상 파일"
|
|
653
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
654
|
-
>
|
|
655
|
-
<Film size={iconSize} />
|
|
656
|
-
</button>
|
|
657
|
-
{/if}
|
|
658
|
-
{#if has('file') && onFileClick}
|
|
659
|
-
<button
|
|
660
|
-
type="button"
|
|
661
|
-
onclick={onFileClick}
|
|
662
|
-
data-tooltip="파일 첨부"
|
|
663
|
-
aria-label="파일 첨부"
|
|
664
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
665
|
-
>
|
|
666
|
-
<Paperclip size={iconSize} />
|
|
667
|
-
</button>
|
|
668
|
-
{/if}
|
|
669
|
-
|
|
670
|
-
<!-- Columns -->
|
|
671
|
-
{#if has('columns-2')}
|
|
672
|
-
<button
|
|
673
|
-
type="button"
|
|
674
|
-
onclick={() => editor.chain().focus().setColumns(2).run()}
|
|
675
|
-
data-tooltip="2단 컬럼"
|
|
676
|
-
aria-label="2단 컬럼"
|
|
677
|
-
class={cn(
|
|
678
|
-
"p-1.5 rounded-md transition-colors",
|
|
679
|
-
editor.isActive("columns")
|
|
680
|
-
? "bg-primary/10 text-primary"
|
|
681
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
682
|
-
)}
|
|
683
|
-
>
|
|
684
|
-
<Columns2 size={iconSize} />
|
|
685
|
-
</button>
|
|
686
|
-
{/if}
|
|
687
|
-
{#if has('columns-3')}
|
|
688
|
-
<button
|
|
689
|
-
type="button"
|
|
690
|
-
onclick={() => editor.chain().focus().setColumns(3).run()}
|
|
691
|
-
data-tooltip="3단 컬럼"
|
|
692
|
-
aria-label="3단 컬럼"
|
|
693
|
-
class="p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
694
|
-
>
|
|
695
|
-
<Columns3 size={iconSize} />
|
|
696
|
-
</button>
|
|
697
|
-
{/if}
|
|
698
|
-
|
|
699
|
-
<!-- Table menu -->
|
|
700
|
-
{#if has('table')}
|
|
701
|
-
<div bind:this={tableMenuEl} class="relative">
|
|
203
|
+
{#if has('redo')}
|
|
702
204
|
<button
|
|
703
205
|
type="button"
|
|
704
|
-
onclick={() => (
|
|
705
|
-
|
|
706
|
-
|
|
206
|
+
onclick={() => editor.chain().focus().redo().run()}
|
|
207
|
+
disabled={!editor.can().redo()}
|
|
208
|
+
data-tooltip="다시 실행"
|
|
209
|
+
aria-label="다시 실행"
|
|
707
210
|
class={cn(
|
|
708
|
-
"
|
|
709
|
-
|
|
710
|
-
? "bg-primary/10 text-primary"
|
|
711
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
211
|
+
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
212
|
+
!editor.can().redo() && "opacity-30 pointer-events-none",
|
|
712
213
|
)}
|
|
713
214
|
>
|
|
714
|
-
<
|
|
715
|
-
<ChevronDown size={12} />
|
|
215
|
+
<Redo size={iconSize} />
|
|
716
216
|
</button>
|
|
717
|
-
{
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
217
|
+
{/if}
|
|
218
|
+
</div>
|
|
219
|
+
{/if}
|
|
220
|
+
|
|
221
|
+
{#if hasBlockItems}
|
|
222
|
+
<!-- Block type selector -->
|
|
223
|
+
<div class="hce-toolbar-group">
|
|
224
|
+
<div bind:this={blockMenuEl} class="relative">
|
|
225
|
+
<button
|
|
226
|
+
type="button"
|
|
227
|
+
onclick={() => (blockMenuOpen = !blockMenuOpen)}
|
|
228
|
+
data-tooltip="블록 타입"
|
|
229
|
+
aria-label="블록 타입"
|
|
230
|
+
class="flex items-center gap-1 px-2 py-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground min-w-[96px]"
|
|
721
231
|
>
|
|
722
|
-
{
|
|
232
|
+
<span class="text-sm">{currentBlockLabel}</span>
|
|
233
|
+
<ChevronDown size={12} />
|
|
234
|
+
</button>
|
|
235
|
+
{#if blockMenuOpen}
|
|
236
|
+
<div
|
|
237
|
+
class="absolute top-full left-0 mt-1 bg-popover border border-border rounded-lg shadow-lg z-50 py-1"
|
|
238
|
+
style="min-width: 200px"
|
|
239
|
+
>
|
|
723
240
|
<button
|
|
724
241
|
type="button"
|
|
725
|
-
class=
|
|
242
|
+
class={cn(
|
|
243
|
+
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
244
|
+
!editor.isActive("heading") &&
|
|
245
|
+
!editor.isActive("bulletList") &&
|
|
246
|
+
!editor.isActive("orderedList") &&
|
|
247
|
+
!editor.isActive("taskList") &&
|
|
248
|
+
!editor.isActive("blockquote") &&
|
|
249
|
+
!editor.isActive("codeBlock") &&
|
|
250
|
+
!editor.isActive("details") &&
|
|
251
|
+
"bg-primary/10 text-primary",
|
|
252
|
+
)}
|
|
726
253
|
onclick={() =>
|
|
727
|
-
|
|
728
|
-
editor
|
|
729
|
-
.chain()
|
|
730
|
-
.focus()
|
|
731
|
-
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
|
|
732
|
-
.run(),
|
|
733
|
-
)}
|
|
254
|
+
runBlock(() => editor.chain().focus().setParagraph().run())}
|
|
734
255
|
>
|
|
735
|
-
<
|
|
736
|
-
표 삽입 (3x3)
|
|
256
|
+
<Pilcrow size={14} /> 본문
|
|
737
257
|
</button>
|
|
258
|
+
{#if has('h1')}
|
|
738
259
|
<button
|
|
739
260
|
type="button"
|
|
740
|
-
class=
|
|
261
|
+
class={cn(
|
|
262
|
+
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
263
|
+
editor.isActive("heading", { level: 1 }) && "bg-primary/10 text-primary",
|
|
264
|
+
)}
|
|
741
265
|
onclick={() =>
|
|
742
|
-
|
|
743
|
-
editor
|
|
744
|
-
.chain()
|
|
745
|
-
.focus()
|
|
746
|
-
.insertTable({ rows: 4, cols: 4, withHeaderRow: true })
|
|
747
|
-
.run(),
|
|
266
|
+
runBlock(() =>
|
|
267
|
+
editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
|
748
268
|
)}
|
|
749
269
|
>
|
|
750
|
-
<
|
|
751
|
-
헤더 포함 4x4
|
|
270
|
+
<Heading1 size={14} /> 제목 1
|
|
752
271
|
</button>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
class="px-3 py-1 text-xs font-semibold text-muted-foreground"
|
|
756
|
-
>
|
|
757
|
-
구조
|
|
758
|
-
</p>
|
|
272
|
+
{/if}
|
|
273
|
+
{#if has('h2')}
|
|
759
274
|
<button
|
|
760
275
|
type="button"
|
|
761
|
-
disabled={!editor.can().addRowBefore()}
|
|
762
276
|
class={cn(
|
|
763
277
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
764
|
-
|
|
278
|
+
editor.isActive("heading", { level: 2 }) && "bg-primary/10 text-primary",
|
|
765
279
|
)}
|
|
766
280
|
onclick={() =>
|
|
767
|
-
|
|
768
|
-
editor.chain().focus().
|
|
281
|
+
runBlock(() =>
|
|
282
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
769
283
|
)}
|
|
770
284
|
>
|
|
771
|
-
<
|
|
285
|
+
<Heading2 size={14} /> 제목 2
|
|
772
286
|
</button>
|
|
287
|
+
{/if}
|
|
288
|
+
{#if has('h3')}
|
|
773
289
|
<button
|
|
774
290
|
type="button"
|
|
775
|
-
disabled={!editor.can().addRowAfter()}
|
|
776
291
|
class={cn(
|
|
777
292
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
778
|
-
|
|
293
|
+
editor.isActive("heading", { level: 3 }) && "bg-primary/10 text-primary",
|
|
779
294
|
)}
|
|
780
295
|
onclick={() =>
|
|
781
|
-
|
|
782
|
-
editor.chain().focus().
|
|
296
|
+
runBlock(() =>
|
|
297
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
783
298
|
)}
|
|
784
299
|
>
|
|
785
|
-
<
|
|
300
|
+
<Heading3 size={14} /> 제목 3
|
|
786
301
|
</button>
|
|
302
|
+
{/if}
|
|
303
|
+
{#if has('bullet-list') || has('ordered-list') || has('checklist')}
|
|
304
|
+
<div class="h-px bg-border my-1"></div>
|
|
305
|
+
{/if}
|
|
306
|
+
{#if has('bullet-list')}
|
|
787
307
|
<button
|
|
788
308
|
type="button"
|
|
789
|
-
disabled={!editor.can().addColumnBefore()}
|
|
790
309
|
class={cn(
|
|
791
310
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
792
|
-
|
|
793
|
-
"opacity-30 pointer-events-none",
|
|
311
|
+
editor.isActive("bulletList") && "bg-primary/10 text-primary",
|
|
794
312
|
)}
|
|
795
313
|
onclick={() =>
|
|
796
|
-
|
|
797
|
-
editor.chain().focus().addColumnBefore().run(),
|
|
798
|
-
)}
|
|
314
|
+
runBlock(() => editor.chain().focus().toggleBulletList().run())}
|
|
799
315
|
>
|
|
800
|
-
<
|
|
316
|
+
<List size={14} /> 글머리 목록
|
|
801
317
|
</button>
|
|
318
|
+
{/if}
|
|
319
|
+
{#if has('ordered-list')}
|
|
802
320
|
<button
|
|
803
321
|
type="button"
|
|
804
|
-
disabled={!editor.can().addColumnAfter()}
|
|
805
322
|
class={cn(
|
|
806
323
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
807
|
-
|
|
808
|
-
"opacity-30 pointer-events-none",
|
|
324
|
+
editor.isActive("orderedList") && "bg-primary/10 text-primary",
|
|
809
325
|
)}
|
|
810
326
|
onclick={() =>
|
|
811
|
-
|
|
812
|
-
editor.chain().focus().addColumnAfter().run(),
|
|
813
|
-
)}
|
|
327
|
+
runBlock(() => editor.chain().focus().toggleOrderedList().run())}
|
|
814
328
|
>
|
|
815
|
-
<
|
|
329
|
+
<ListOrdered size={14} /> 번호 목록
|
|
816
330
|
</button>
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
class="px-3 py-1 text-xs font-semibold text-muted-foreground"
|
|
820
|
-
>
|
|
821
|
-
헤더 / 셀
|
|
822
|
-
</p>
|
|
331
|
+
{/if}
|
|
332
|
+
{#if has('checklist')}
|
|
823
333
|
<button
|
|
824
334
|
type="button"
|
|
825
|
-
disabled={!editor.can().toggleHeaderRow()}
|
|
826
335
|
class={cn(
|
|
827
336
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
828
|
-
|
|
829
|
-
"opacity-30 pointer-events-none",
|
|
337
|
+
editor.isActive("taskList") && "bg-primary/10 text-primary",
|
|
830
338
|
)}
|
|
831
339
|
onclick={() =>
|
|
832
|
-
|
|
833
|
-
editor.chain().focus().toggleHeaderRow().run(),
|
|
834
|
-
)}
|
|
340
|
+
runBlock(() => editor.chain().focus().toggleTaskList().run())}
|
|
835
341
|
>
|
|
836
|
-
<
|
|
342
|
+
<CheckSquare size={14} /> 체크리스트
|
|
837
343
|
</button>
|
|
344
|
+
{/if}
|
|
345
|
+
{#if has('blockquote') || has('toggle')}
|
|
346
|
+
<div class="h-px bg-border my-1"></div>
|
|
347
|
+
{/if}
|
|
348
|
+
{#if has('blockquote')}
|
|
838
349
|
<button
|
|
839
350
|
type="button"
|
|
840
|
-
disabled={!editor.can().toggleHeaderColumn()}
|
|
841
351
|
class={cn(
|
|
842
352
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
843
|
-
|
|
844
|
-
"opacity-30 pointer-events-none",
|
|
353
|
+
editor.isActive("blockquote") && "bg-primary/10 text-primary",
|
|
845
354
|
)}
|
|
846
355
|
onclick={() =>
|
|
847
|
-
|
|
848
|
-
editor.chain().focus().toggleHeaderColumn().run(),
|
|
849
|
-
)}
|
|
356
|
+
runBlock(() => editor.chain().focus().toggleBlockquote().run())}
|
|
850
357
|
>
|
|
851
|
-
<
|
|
358
|
+
<Quote size={14} /> 인용문
|
|
852
359
|
</button>
|
|
360
|
+
{/if}
|
|
361
|
+
{#if has('toggle')}
|
|
853
362
|
<button
|
|
854
363
|
type="button"
|
|
855
|
-
disabled={!editor.can().mergeCells()}
|
|
856
364
|
class={cn(
|
|
857
365
|
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
858
|
-
|
|
366
|
+
editor.isActive("details") && "bg-primary/10 text-primary",
|
|
859
367
|
)}
|
|
860
368
|
onclick={() =>
|
|
861
|
-
|
|
862
|
-
editor.chain().focus().mergeCells().run(),
|
|
863
|
-
)}
|
|
369
|
+
runBlock(() => editor.chain().focus().setDetails().run())}
|
|
864
370
|
>
|
|
865
|
-
<
|
|
371
|
+
<ChevronRight size={14} /> 토글
|
|
866
372
|
</button>
|
|
373
|
+
{/if}
|
|
374
|
+
</div>
|
|
375
|
+
{/if}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
{/if}
|
|
379
|
+
|
|
380
|
+
{#if has('bold') || has('italic') || has('underline') || has('strike')}
|
|
381
|
+
<!-- Inline marks (core 4) -->
|
|
382
|
+
<div class="hce-toolbar-group">
|
|
383
|
+
{#if has('bold')}
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
onclick={() => editor.chain().focus().toggleBold().run()}
|
|
387
|
+
data-tooltip="굵게"
|
|
388
|
+
aria-label="굵게"
|
|
389
|
+
class={cn(
|
|
390
|
+
"p-1.5 rounded-md transition-colors",
|
|
391
|
+
editor.isActive("bold")
|
|
392
|
+
? "bg-primary/10 text-primary"
|
|
393
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
394
|
+
)}
|
|
395
|
+
>
|
|
396
|
+
<Bold size={iconSize} />
|
|
397
|
+
</button>
|
|
398
|
+
{/if}
|
|
399
|
+
{#if has('italic')}
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
onclick={() => editor.chain().focus().toggleItalic().run()}
|
|
403
|
+
data-tooltip="기울임"
|
|
404
|
+
aria-label="기울임"
|
|
405
|
+
class={cn(
|
|
406
|
+
"p-1.5 rounded-md transition-colors",
|
|
407
|
+
editor.isActive("italic")
|
|
408
|
+
? "bg-primary/10 text-primary"
|
|
409
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
410
|
+
)}
|
|
411
|
+
>
|
|
412
|
+
<Italic size={iconSize} />
|
|
413
|
+
</button>
|
|
414
|
+
{/if}
|
|
415
|
+
{#if has('underline')}
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
onclick={() => editor.chain().focus().toggleUnderline().run()}
|
|
419
|
+
data-tooltip="밑줄"
|
|
420
|
+
aria-label="밑줄"
|
|
421
|
+
class={cn(
|
|
422
|
+
"p-1.5 rounded-md transition-colors",
|
|
423
|
+
editor.isActive("underline")
|
|
424
|
+
? "bg-primary/10 text-primary"
|
|
425
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
426
|
+
)}
|
|
427
|
+
>
|
|
428
|
+
<UnderlineIcon size={iconSize} />
|
|
429
|
+
</button>
|
|
430
|
+
{/if}
|
|
431
|
+
{#if has('strike')}
|
|
432
|
+
<button
|
|
433
|
+
type="button"
|
|
434
|
+
onclick={() => editor.chain().focus().toggleStrike().run()}
|
|
435
|
+
data-tooltip="취소선"
|
|
436
|
+
aria-label="취소선"
|
|
437
|
+
class={cn(
|
|
438
|
+
"p-1.5 rounded-md transition-colors",
|
|
439
|
+
editor.isActive("strike")
|
|
440
|
+
? "bg-primary/10 text-primary"
|
|
441
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
442
|
+
)}
|
|
443
|
+
>
|
|
444
|
+
<Strikethrough size={iconSize} />
|
|
445
|
+
</button>
|
|
446
|
+
{/if}
|
|
447
|
+
</div>
|
|
448
|
+
{/if}
|
|
449
|
+
|
|
450
|
+
{#if has('align-left') || has('align-center') || has('align-right')}
|
|
451
|
+
<!-- Alignment -->
|
|
452
|
+
<div class="hce-toolbar-group">
|
|
453
|
+
{#if has('align-left')}
|
|
454
|
+
<button
|
|
455
|
+
type="button"
|
|
456
|
+
onclick={() => editor.chain().focus().setTextAlign('left').run()}
|
|
457
|
+
data-tooltip="왼쪽 정렬"
|
|
458
|
+
aria-label="왼쪽 정렬"
|
|
459
|
+
class={cn(
|
|
460
|
+
"p-1.5 rounded-md transition-colors",
|
|
461
|
+
editor.isActive({ textAlign: 'left' })
|
|
462
|
+
? "bg-primary/10 text-primary"
|
|
463
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
464
|
+
)}
|
|
465
|
+
>
|
|
466
|
+
<AlignLeft size={iconSize} />
|
|
467
|
+
</button>
|
|
468
|
+
{/if}
|
|
469
|
+
{#if has('align-center')}
|
|
470
|
+
<button
|
|
471
|
+
type="button"
|
|
472
|
+
onclick={() => editor.chain().focus().setTextAlign('center').run()}
|
|
473
|
+
data-tooltip="가운데 정렬"
|
|
474
|
+
aria-label="가운데 정렬"
|
|
475
|
+
class={cn(
|
|
476
|
+
"p-1.5 rounded-md transition-colors",
|
|
477
|
+
editor.isActive({ textAlign: 'center' })
|
|
478
|
+
? "bg-primary/10 text-primary"
|
|
479
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
480
|
+
)}
|
|
481
|
+
>
|
|
482
|
+
<AlignCenter size={iconSize} />
|
|
483
|
+
</button>
|
|
484
|
+
{/if}
|
|
485
|
+
{#if has('align-right')}
|
|
486
|
+
<button
|
|
487
|
+
type="button"
|
|
488
|
+
onclick={() => editor.chain().focus().setTextAlign('right').run()}
|
|
489
|
+
data-tooltip="오른쪽 정렬"
|
|
490
|
+
aria-label="오른쪽 정렬"
|
|
491
|
+
class={cn(
|
|
492
|
+
"p-1.5 rounded-md transition-colors",
|
|
493
|
+
editor.isActive({ textAlign: 'right' })
|
|
494
|
+
? "bg-primary/10 text-primary"
|
|
495
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
496
|
+
)}
|
|
497
|
+
>
|
|
498
|
+
<AlignRight size={iconSize} />
|
|
499
|
+
</button>
|
|
500
|
+
{/if}
|
|
501
|
+
</div>
|
|
502
|
+
{/if}
|
|
503
|
+
|
|
504
|
+
{#if hasInsertItems}
|
|
505
|
+
<!-- Insert + dropdown -->
|
|
506
|
+
<div class="hce-toolbar-group">
|
|
507
|
+
<div bind:this={insertMenuEl} class="relative">
|
|
508
|
+
<button
|
|
509
|
+
type="button"
|
|
510
|
+
onclick={() => (insertMenuOpen = !insertMenuOpen)}
|
|
511
|
+
data-tooltip="삽입"
|
|
512
|
+
aria-label="삽입"
|
|
513
|
+
class="flex items-center gap-1 px-2 py-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
514
|
+
>
|
|
515
|
+
<Plus size={iconSize} />
|
|
516
|
+
<span class="text-sm">삽입</span>
|
|
517
|
+
<ChevronDown size={12} />
|
|
518
|
+
</button>
|
|
519
|
+
{#if insertMenuOpen}
|
|
520
|
+
<div
|
|
521
|
+
class="absolute top-full left-0 mt-1 bg-popover border border-border rounded-lg shadow-lg z-50 py-1"
|
|
522
|
+
style="min-width: 220px"
|
|
523
|
+
>
|
|
524
|
+
{#if has('code-block')}
|
|
867
525
|
<button
|
|
868
526
|
type="button"
|
|
869
|
-
|
|
870
|
-
class={cn(
|
|
871
|
-
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted",
|
|
872
|
-
!editor.can().splitCell() && "opacity-30 pointer-events-none",
|
|
873
|
-
)}
|
|
527
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
874
528
|
onclick={() =>
|
|
875
|
-
|
|
876
|
-
editor.chain().focus().splitCell().run(),
|
|
877
|
-
)}
|
|
529
|
+
runInsert(() => editor.chain().focus().setCodeBlock().run())}
|
|
878
530
|
>
|
|
879
|
-
<
|
|
531
|
+
<Code2 size={14} /> 코드 블록
|
|
880
532
|
</button>
|
|
533
|
+
{/if}
|
|
534
|
+
{#if has('pdf')}
|
|
881
535
|
<button
|
|
882
536
|
type="button"
|
|
883
537
|
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
884
|
-
onclick={() =>
|
|
885
|
-
tableMenuOpen = false;
|
|
886
|
-
modalState = { type: "cellBg" };
|
|
887
|
-
}}
|
|
538
|
+
onclick={() => runInsert(onPdfClick)}
|
|
888
539
|
>
|
|
889
|
-
<
|
|
540
|
+
<FileText size={14} /> PDF
|
|
890
541
|
</button>
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
542
|
+
{/if}
|
|
543
|
+
{#if has('file') && onFileClick}
|
|
544
|
+
<button
|
|
545
|
+
type="button"
|
|
546
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
547
|
+
onclick={() => runInsert(onFileClick!)}
|
|
894
548
|
>
|
|
895
|
-
|
|
896
|
-
</
|
|
549
|
+
<Paperclip size={14} /> 파일 첨부
|
|
550
|
+
</button>
|
|
551
|
+
{/if}
|
|
552
|
+
{#if has('table')}
|
|
897
553
|
<button
|
|
898
554
|
type="button"
|
|
899
|
-
|
|
900
|
-
class={cn(
|
|
901
|
-
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 text-destructive hover:bg-destructive/10",
|
|
902
|
-
!editor.can().deleteRow() && "opacity-30 pointer-events-none",
|
|
903
|
-
)}
|
|
555
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
904
556
|
onclick={() =>
|
|
905
|
-
|
|
906
|
-
editor
|
|
557
|
+
runInsert(() =>
|
|
558
|
+
editor
|
|
559
|
+
.chain()
|
|
560
|
+
.focus()
|
|
561
|
+
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
|
|
562
|
+
.run(),
|
|
907
563
|
)}
|
|
908
564
|
>
|
|
909
|
-
<
|
|
565
|
+
<TableIcon size={14} /> 표 (3x3)
|
|
910
566
|
</button>
|
|
567
|
+
{/if}
|
|
568
|
+
|
|
569
|
+
{#if has('horizontal-rule') || has('columns-2') || has('columns-3')}
|
|
570
|
+
<div class="h-px bg-border my-1"></div>
|
|
571
|
+
{/if}
|
|
572
|
+
{#if has('horizontal-rule')}
|
|
911
573
|
<button
|
|
912
574
|
type="button"
|
|
913
|
-
|
|
914
|
-
class={cn(
|
|
915
|
-
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 text-destructive hover:bg-destructive/10",
|
|
916
|
-
!editor.can().deleteColumn() && "opacity-30 pointer-events-none",
|
|
917
|
-
)}
|
|
575
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
918
576
|
onclick={() =>
|
|
919
|
-
|
|
920
|
-
editor.chain().focus().
|
|
577
|
+
runInsert(() =>
|
|
578
|
+
editor.chain().focus().setHorizontalRule().run(),
|
|
921
579
|
)}
|
|
922
580
|
>
|
|
923
|
-
<
|
|
581
|
+
<Minus size={14} /> 구분선
|
|
924
582
|
</button>
|
|
583
|
+
{/if}
|
|
584
|
+
{#if has('columns-2')}
|
|
925
585
|
<button
|
|
926
586
|
type="button"
|
|
927
|
-
|
|
928
|
-
class={cn(
|
|
929
|
-
"w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 text-destructive hover:bg-destructive/10",
|
|
930
|
-
!editor.can().deleteTable() && "opacity-30 pointer-events-none",
|
|
931
|
-
)}
|
|
587
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
932
588
|
onclick={() =>
|
|
933
|
-
|
|
934
|
-
editor.chain().focus().deleteTable().run(),
|
|
935
|
-
)}
|
|
589
|
+
runInsert(() => editor.chain().focus().setColumns(2).run())}
|
|
936
590
|
>
|
|
937
|
-
<
|
|
591
|
+
<Columns2 size={14} /> 2단 컬럼
|
|
938
592
|
</button>
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
data-tooltip="코드 블록"
|
|
951
|
-
aria-label="코드 블록"
|
|
952
|
-
class={cn(
|
|
953
|
-
"p-1.5 rounded-md transition-colors",
|
|
954
|
-
editor.isActive("codeBlock")
|
|
955
|
-
? "bg-primary/10 text-primary"
|
|
956
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
957
|
-
)}
|
|
958
|
-
>
|
|
959
|
-
<Code2 size={iconSize} />
|
|
960
|
-
</button>
|
|
961
|
-
{/if}
|
|
593
|
+
{/if}
|
|
594
|
+
{#if has('columns-3')}
|
|
595
|
+
<button
|
|
596
|
+
type="button"
|
|
597
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
598
|
+
onclick={() =>
|
|
599
|
+
runInsert(() => editor.chain().focus().setColumns(3).run())}
|
|
600
|
+
>
|
|
601
|
+
<Columns3 size={14} /> 3단 컬럼
|
|
602
|
+
</button>
|
|
603
|
+
{/if}
|
|
962
604
|
|
|
605
|
+
{#if has('image') || has('link') || has('mbus')}
|
|
606
|
+
<div class="h-px bg-border my-1"></div>
|
|
607
|
+
{/if}
|
|
608
|
+
{#if has('image')}
|
|
609
|
+
<button
|
|
610
|
+
type="button"
|
|
611
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
612
|
+
onclick={addImage}
|
|
613
|
+
>
|
|
614
|
+
<ImageIcon size={14} /> 이미지
|
|
615
|
+
</button>
|
|
616
|
+
{/if}
|
|
617
|
+
{#if has('link')}
|
|
618
|
+
<button
|
|
619
|
+
type="button"
|
|
620
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
621
|
+
onclick={addLink}
|
|
622
|
+
>
|
|
623
|
+
<LinkIcon size={14} /> 링크
|
|
624
|
+
</button>
|
|
625
|
+
{/if}
|
|
626
|
+
{#if has('mbus')}
|
|
627
|
+
<button
|
|
628
|
+
type="button"
|
|
629
|
+
class="w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 hover:bg-muted"
|
|
630
|
+
onclick={addMbus}
|
|
631
|
+
>
|
|
632
|
+
<Tv size={14} /> 미디버스 영상
|
|
633
|
+
</button>
|
|
634
|
+
{/if}
|
|
635
|
+
</div>
|
|
636
|
+
{/if}
|
|
963
637
|
</div>
|
|
964
|
-
{/if}
|
|
965
|
-
|
|
966
|
-
{#if has('undo') || has('redo')}
|
|
967
|
-
<!-- Undo / Redo -->
|
|
968
|
-
<div class="hce-toolbar-group">
|
|
969
|
-
|
|
970
|
-
{#if has('undo')}
|
|
971
|
-
<button
|
|
972
|
-
type="button"
|
|
973
|
-
onclick={() => editor.chain().focus().undo().run()}
|
|
974
|
-
disabled={!editor.can().undo()}
|
|
975
|
-
data-tooltip="실행 취소"
|
|
976
|
-
aria-label="실행 취소"
|
|
977
|
-
class={cn(
|
|
978
|
-
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
979
|
-
!editor.can().undo() && "opacity-30 pointer-events-none",
|
|
980
|
-
)}
|
|
981
|
-
>
|
|
982
|
-
<Undo size={iconSize} />
|
|
983
|
-
</button>
|
|
984
|
-
{/if}
|
|
985
|
-
{#if has('redo')}
|
|
986
|
-
<button
|
|
987
|
-
type="button"
|
|
988
|
-
onclick={() => editor.chain().focus().redo().run()}
|
|
989
|
-
disabled={!editor.can().redo()}
|
|
990
|
-
data-tooltip="다시 실행"
|
|
991
|
-
aria-label="다시 실행"
|
|
992
|
-
class={cn(
|
|
993
|
-
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
994
|
-
!editor.can().redo() && "opacity-30 pointer-events-none",
|
|
995
|
-
)}
|
|
996
|
-
>
|
|
997
|
-
<Redo size={iconSize} />
|
|
998
|
-
</button>
|
|
999
|
-
{/if}
|
|
1000
|
-
|
|
1001
638
|
</div>
|
|
1002
639
|
{/if}
|
|
1003
640
|
|
|
@@ -1041,47 +678,6 @@
|
|
|
1041
678
|
onCancel={() => (modalState = null)}
|
|
1042
679
|
/>
|
|
1043
680
|
{/if}
|
|
1044
|
-
{#if modalState?.type === "cellBg"}
|
|
1045
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1046
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1047
|
-
<div
|
|
1048
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-black/30"
|
|
1049
|
-
onclick={() => (modalState = null)}
|
|
1050
|
-
>
|
|
1051
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1052
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1053
|
-
<div
|
|
1054
|
-
class="bg-popover border border-border rounded-xl shadow-lg p-4"
|
|
1055
|
-
style="min-width: 240px"
|
|
1056
|
-
onclick={(e) => e.stopPropagation()}
|
|
1057
|
-
>
|
|
1058
|
-
<p class="text-sm font-semibold mb-3">셀 배경색</p>
|
|
1059
|
-
<div class="grid grid-cols-3 gap-2">
|
|
1060
|
-
{#each CELL_COLORS as c}
|
|
1061
|
-
<button
|
|
1062
|
-
type="button"
|
|
1063
|
-
title={c.label}
|
|
1064
|
-
class="h-9 rounded-lg border border-border transition-transform hover:scale-105 flex items-center justify-center text-xs"
|
|
1065
|
-
style="background: {c.value ||
|
|
1066
|
-
'#fff'}; {!c.value
|
|
1067
|
-
? 'background-image: linear-gradient(135deg, transparent 45%, #ef4444 45%, #ef4444 55%, transparent 55%)'
|
|
1068
|
-
: ''}"
|
|
1069
|
-
onclick={() => {
|
|
1070
|
-
editor
|
|
1071
|
-
.chain()
|
|
1072
|
-
.focus()
|
|
1073
|
-
.setCellAttribute("backgroundColor", c.value || null)
|
|
1074
|
-
.run();
|
|
1075
|
-
modalState = null;
|
|
1076
|
-
}}
|
|
1077
|
-
>
|
|
1078
|
-
{c.value ? "" : ""}
|
|
1079
|
-
</button>
|
|
1080
|
-
{/each}
|
|
1081
|
-
</div>
|
|
1082
|
-
</div>
|
|
1083
|
-
</div>
|
|
1084
|
-
{/if}
|
|
1085
681
|
</div>
|
|
1086
682
|
|
|
1087
683
|
<style>
|