@teriusu/rich-editor 0.8.4 → 0.9.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/dist/components/FixedToolbar.svelte +50 -14
- package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
- package/dist/components/PdfViewer.svelte +14 -9
- package/dist/components/PdfViewer.svelte.d.ts +1 -0
- package/dist/components/PdfViewer.svelte.d.ts.map +1 -1
- package/dist/components/TipTapEditor.svelte +40 -4
- package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
- package/dist/extensions/PdfBlock.d.ts.map +1 -1
- package/dist/extensions/PdfBlock.js +176 -11
- package/dist/extensions/PdfBlock.test.js +30 -0
- package/dist/styles/editor.css +1 -1
- package/dist/types.d.ts +10 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -3
- package/package.json +1 -1
|
@@ -89,6 +89,17 @@
|
|
|
89
89
|
});
|
|
90
90
|
const isActive = (...args: Parameters<Editor["isActive"]>) =>
|
|
91
91
|
(tick, editor.isActive(...args));
|
|
92
|
+
/**
|
|
93
|
+
* ⚠️ `isActive` 만 고쳐서는 부족했다. **되돌리기 가능 여부**와 **지금 글자색**도
|
|
94
|
+
* 매 트랜잭션마다 다시 읽어야 하는데, 그냥 `editor.can()` / `editor.getAttributes()` 를
|
|
95
|
+
* 부르면 Svelte 가 의존성을 못 봐서 처음 값에 굳는다 —
|
|
96
|
+
* 되돌리기가 영영 비활성으로 남고(사용자 지적), 글자색 버튼은 한 번 켜지면 색을 꺼도
|
|
97
|
+
* 계속 켜진 채였다.
|
|
98
|
+
*/
|
|
99
|
+
const canDo = (fn: (c: ReturnType<Editor["can"]>) => boolean) =>
|
|
100
|
+
(tick, fn(editor.can()));
|
|
101
|
+
const attrOf = (name: string, key: string) =>
|
|
102
|
+
(tick, editor.getAttributes(name)[key] as string | undefined);
|
|
92
103
|
|
|
93
104
|
const has = (f: ToolbarFeature) => features.has(f);
|
|
94
105
|
|
|
@@ -102,18 +113,40 @@
|
|
|
102
113
|
let insertMenuEl: HTMLDivElement | undefined = $state();
|
|
103
114
|
let colorMenuEl: HTMLDivElement | undefined = $state();
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
/*
|
|
117
|
+
* ⚠️ **`검정(#000000)` 은 뺐다.** `기본`(×, 색 지정 해제)과 눈으로 구별이 안 되는데
|
|
118
|
+
* (본문 기본색이 거의 검정이다) 두 가지가 다 있으면 헷갈린다 — 사용자가 짚은 부분이다.
|
|
119
|
+
*
|
|
120
|
+
* 남기는 쪽은 **`기본`** 이다. `검정` 만 남기면 본문 기본색으로 **되돌릴 방법이 없어지고**,
|
|
121
|
+
* 저장된 HTML 에 `#000000` 이 눌러앉는다. 그 글은 다크 모드에서 검정 글자가 되어
|
|
122
|
+
* 안 보인다(코드패스가 `sanitizeBodyColors` 로 이 자국을 걷어내고 있는 이유다).
|
|
123
|
+
*
|
|
124
|
+
* `브랜드` 는 앱마다 다르므로 값을 고정하지 않고 `--primary` 를 읽는다 —
|
|
125
|
+
* 정올과 코드패스가 각자 자기 색을 얻는다. 저장은 계산된 실제 색으로 한다
|
|
126
|
+
* (`var(--primary)` 를 그대로 저장하면 앱 밖에서 렌더할 때 색이 사라진다).
|
|
127
|
+
*
|
|
128
|
+
* ⚠️ **아홉 개로 맞춰 둔다.** 팔레트가 `grid-cols-3` 라 여덟 개면 마지막 줄에 빈 칸이
|
|
129
|
+
* 남아 덜 만든 것처럼 보인다(사용자 지적). `기본` + 색 여덟이 3×3 을 정확히 채운다.
|
|
130
|
+
* 색은 색상환을 고르게 훑도록 골랐다 — 빨강·주황·노랑·초록·파랑에 브랜드(남보라)와
|
|
131
|
+
* 그 사이를 메우는 분홍, 그리고 무채색 회색. 여기서 더 늘리려면 12개(3×4)로 가야 한다.
|
|
132
|
+
*/
|
|
133
|
+
const TEXT_COLORS: { label: string; value: string; cssVar?: string }[] = [
|
|
106
134
|
{ label: "기본", value: "" },
|
|
107
|
-
{ label: "
|
|
135
|
+
{ label: "브랜드", value: "", cssVar: "--primary" },
|
|
108
136
|
{ label: "회색", value: "#6b7280" },
|
|
109
137
|
{ label: "빨강", value: "#dc2626" },
|
|
110
138
|
{ label: "주황", value: "#ea580c" },
|
|
111
139
|
{ label: "노랑", value: "#ca8a04" },
|
|
112
140
|
{ label: "초록", value: "#16a34a" },
|
|
113
141
|
{ label: "파랑", value: "#2563eb" },
|
|
114
|
-
{ label: "
|
|
142
|
+
{ label: "분홍", value: "#db2777" }
|
|
115
143
|
];
|
|
116
144
|
|
|
145
|
+
/** `--primary` 같은 CSS 변수를 그 자리에서 실제 색 문자열로 바꾼다. */
|
|
146
|
+
function resolveCssVar(el: HTMLElement, name: string): string {
|
|
147
|
+
return getComputedStyle(el).getPropertyValue(name).trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
$effect(() => {
|
|
118
151
|
if (!blockMenuOpen && !insertMenuOpen && !colorMenuOpen) return;
|
|
119
152
|
function handleClick(e: MouseEvent) {
|
|
@@ -242,12 +275,12 @@
|
|
|
242
275
|
<button
|
|
243
276
|
type="button"
|
|
244
277
|
onclick={() => editor.chain().focus().undo().run()}
|
|
245
|
-
disabled={!
|
|
278
|
+
disabled={!canDo((c) => c.undo())}
|
|
246
279
|
data-tooltip="실행 취소"
|
|
247
280
|
aria-label="실행 취소"
|
|
248
281
|
class={cn(
|
|
249
282
|
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
250
|
-
!
|
|
283
|
+
!canDo((c) => c.undo()) && "opacity-30 pointer-events-none",
|
|
251
284
|
)}
|
|
252
285
|
>
|
|
253
286
|
<Undo size={iconSize} />
|
|
@@ -257,12 +290,12 @@
|
|
|
257
290
|
<button
|
|
258
291
|
type="button"
|
|
259
292
|
onclick={() => editor.chain().focus().redo().run()}
|
|
260
|
-
disabled={!
|
|
293
|
+
disabled={!canDo((c) => c.redo())}
|
|
261
294
|
data-tooltip="다시 실행"
|
|
262
295
|
aria-label="다시 실행"
|
|
263
296
|
class={cn(
|
|
264
297
|
"p-1.5 rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
265
|
-
!
|
|
298
|
+
!canDo((c) => c.redo()) && "opacity-30 pointer-events-none",
|
|
266
299
|
)}
|
|
267
300
|
>
|
|
268
301
|
<Redo size={iconSize} />
|
|
@@ -509,7 +542,7 @@
|
|
|
509
542
|
aria-label="글자색"
|
|
510
543
|
class={cn(
|
|
511
544
|
"flex items-center gap-0.5 p-1.5 rounded-md transition-colors",
|
|
512
|
-
|
|
545
|
+
attrOf('textStyle', 'color')
|
|
513
546
|
? "hce-active"
|
|
514
547
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
515
548
|
)}
|
|
@@ -531,17 +564,20 @@
|
|
|
531
564
|
type="button"
|
|
532
565
|
title={c.label}
|
|
533
566
|
class="h-8 rounded-md border border-border transition-transform hover:scale-105 flex items-center justify-center text-xs font-bold bg-background"
|
|
534
|
-
style="color: {c.value || '#6b7280'}"
|
|
535
|
-
onclick={() => {
|
|
536
|
-
|
|
537
|
-
|
|
567
|
+
style="color: {c.cssVar ? `var(${c.cssVar})` : c.value || '#6b7280'}"
|
|
568
|
+
onclick={(e) => {
|
|
569
|
+
const color = c.cssVar
|
|
570
|
+
? resolveCssVar(e.currentTarget as HTMLElement, c.cssVar)
|
|
571
|
+
: c.value;
|
|
572
|
+
if (color) {
|
|
573
|
+
editor.chain().focus().setColor(color).run();
|
|
538
574
|
} else {
|
|
539
575
|
editor.chain().focus().unsetColor().run();
|
|
540
576
|
}
|
|
541
577
|
colorMenuOpen = false;
|
|
542
578
|
}}
|
|
543
579
|
>
|
|
544
|
-
{c.value ? "A" : "×"}
|
|
580
|
+
{c.cssVar || c.value ? "A" : "×"}
|
|
545
581
|
</button>
|
|
546
582
|
{/each}
|
|
547
583
|
</div>
|
|
@@ -552,7 +588,7 @@
|
|
|
552
588
|
<input
|
|
553
589
|
type="color"
|
|
554
590
|
class="h-6 w-10 cursor-pointer rounded border border-border bg-transparent p-0"
|
|
555
|
-
value={(
|
|
591
|
+
value={attrOf('textStyle', 'color') || '#000000'}
|
|
556
592
|
onclick={(e) => e.stopPropagation()}
|
|
557
593
|
oninput={(e) => {
|
|
558
594
|
const v = (e.target as HTMLInputElement).value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixedToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FixedToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAqCtC,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"FixedToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FixedToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAqCtC,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AA6lBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
import { ChevronLeft, ChevronRight } from "lucide-svelte";
|
|
4
4
|
import { getPdfJs } from "../utils/pdf";
|
|
5
5
|
|
|
6
|
-
let {
|
|
6
|
+
let {
|
|
7
|
+
src,
|
|
8
|
+
fileName,
|
|
9
|
+
label
|
|
10
|
+
}: { src: string; fileName?: string; label?: string } = $props();
|
|
7
11
|
|
|
8
12
|
const cleanName = (raw?: string) =>
|
|
9
13
|
(raw || "").replace(/[?#].*$/, "").trim() || "PDF 문서";
|
|
10
14
|
|
|
15
|
+
/** `label` 이 있으면 그것, 없으면 파일명. */
|
|
16
|
+
const displayName = $derived(label?.trim() || cleanName(fileName));
|
|
17
|
+
|
|
11
18
|
let canvasEl: HTMLCanvasElement | undefined = $state();
|
|
12
19
|
let containerEl: HTMLDivElement | undefined = $state();
|
|
13
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -105,14 +112,12 @@
|
|
|
105
112
|
<p class="text-sm text-muted-foreground animate-pulse">PDF 로딩 중...</p>
|
|
106
113
|
</div>
|
|
107
114
|
{:else}
|
|
108
|
-
<div class="border border-border rounded-lg overflow-hidden my-
|
|
109
|
-
<div
|
|
110
|
-
class="
|
|
111
|
-
|
|
112
|
-
<span class="text-xs text-muted-foreground truncate">
|
|
113
|
-
{cleanName(fileName)}
|
|
115
|
+
<div class="border border-border rounded-lg overflow-hidden my-2">
|
|
116
|
+
<div class="flex items-center gap-2 px-3 py-1 border-b border-border bg-muted">
|
|
117
|
+
<span class="text-xs text-muted-foreground truncate min-w-0">
|
|
118
|
+
{displayName}
|
|
114
119
|
</span>
|
|
115
|
-
<span class="text-xs text-muted-foreground tabular-nums">
|
|
120
|
+
<span class="text-xs text-muted-foreground tabular-nums ml-auto shrink-0">
|
|
116
121
|
{page} / {total}
|
|
117
122
|
</span>
|
|
118
123
|
</div>
|
|
@@ -126,7 +131,7 @@
|
|
|
126
131
|
|
|
127
132
|
{#if total > 1}
|
|
128
133
|
<div
|
|
129
|
-
class="flex items-center justify-center gap-4 px-
|
|
134
|
+
class="flex items-center justify-center gap-4 px-3 py-1 border-t border-border bg-muted"
|
|
130
135
|
>
|
|
131
136
|
<button
|
|
132
137
|
type="button"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdfViewer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/PdfViewer.svelte.ts"],"names":[],"mappings":"AAOC,KAAK,gBAAgB,GAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"PdfViewer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/PdfViewer.svelte.ts"],"names":[],"mappings":"AAOC,KAAK,gBAAgB,GAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA8I7E,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import { FileAttachment } from "../extensions/FileAttachment";
|
|
39
39
|
import { MbusVideo } from "../extensions/MbusVideo";
|
|
40
40
|
import { CardBlock } from "../extensions/CardBlock";
|
|
41
|
+
import { MathInline, MathDisplay } from "../extensions/Math";
|
|
41
42
|
import FixedToolbar from "./FixedToolbar.svelte";
|
|
42
43
|
import BubbleToolbar from "./BubbleToolbar.svelte";
|
|
43
44
|
import SlashCommandMenu from "./SlashCommandMenu.svelte";
|
|
@@ -148,6 +149,18 @@
|
|
|
148
149
|
|
|
149
150
|
let editorElement: HTMLDivElement | undefined = $state();
|
|
150
151
|
let editor: Editor | undefined = $state();
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* 글자수는 `editor.storage` 에서 읽는데 그건 `$state` 가 아니라, 그냥 부르면 첫 값에
|
|
155
|
+
* 굳어 **타이핑해도 숫자가 안 바뀐다**(사용자 지적). 트랜잭션마다 카운터를 올리고
|
|
156
|
+
* 그 카운터를 함께 읽어 Svelte 에 의존성을 알린다 — 툴바 활성 표시와 같은 방식이다.
|
|
157
|
+
*/
|
|
158
|
+
let editorTick = $state(0);
|
|
159
|
+
const counts = $derived.by(() => {
|
|
160
|
+
editorTick;
|
|
161
|
+
const cc = editor?.storage?.characterCount;
|
|
162
|
+
return { chars: cc?.characters?.() ?? 0, words: cc?.words?.() ?? 0 };
|
|
163
|
+
});
|
|
151
164
|
let uploading = $state(false);
|
|
152
165
|
let pdfInputEl: HTMLInputElement | undefined = $state();
|
|
153
166
|
let fileInputEl: HTMLInputElement | undefined = $state();
|
|
@@ -389,6 +402,21 @@
|
|
|
389
402
|
}),
|
|
390
403
|
MbusVideo,
|
|
391
404
|
CardBlock.configure({ promptBackground: onPromptCardBackground ?? null }),
|
|
405
|
+
/*
|
|
406
|
+
* ⚠️ **수학은 여기서 등록한다.** 예전엔 `MathInline`/`MathDisplay` 를 내보내기만 하고
|
|
407
|
+
* 등록은 호스트에 맡겼는데, 그러면 앱마다 기억해서 `extensions` 로 넘겨야 한다 —
|
|
408
|
+
* 정올이 실제로 빠뜨려서 `\le` 같은 수식이 문제 본문에 **원문 그대로** 떴다.
|
|
409
|
+
* 두 앱 다 필요한 것이면 패키지가 갖고 있는 게 맞다.
|
|
410
|
+
*
|
|
411
|
+
* 호스트가 이미 넘겼으면 비켜선다(같은 이름 확장을 두 번 등록하면 TipTap 이 죽는다).
|
|
412
|
+
* `codeBlock` 이 쓰는 방식과 같다.
|
|
413
|
+
*/
|
|
414
|
+
...(extraExtensions.some((ext) => (ext as any).name === "math_inline")
|
|
415
|
+
? []
|
|
416
|
+
: [MathInline]),
|
|
417
|
+
...(extraExtensions.some((ext) => (ext as any).name === "math_display")
|
|
418
|
+
? []
|
|
419
|
+
: [MathDisplay]),
|
|
392
420
|
Columns,
|
|
393
421
|
Column,
|
|
394
422
|
CodeBlockTopEscape,
|
|
@@ -493,8 +521,12 @@
|
|
|
493
521
|
/*
|
|
494
522
|
* ⚠️ 예전엔 여기서 `editor = editor` 로 Svelte 를 밀었다. 그건 Svelte 4 관용구고
|
|
495
523
|
* rune 에서는 **같은 참조 재대입이라 아무 일도 일어나지 않는다** — 툴바 활성 표시가
|
|
496
|
-
* 영영 갱신되지 않았다. 이제
|
|
524
|
+
* 영영 갱신되지 않았다. 툴바는 이제 `transaction` 을 직접 구독하고, 이 컴포넌트가
|
|
525
|
+
* 그리는 글자수는 아래 카운터로 다시 읽는다.
|
|
497
526
|
*/
|
|
527
|
+
onTransaction: () => {
|
|
528
|
+
editorTick++;
|
|
529
|
+
},
|
|
498
530
|
});
|
|
499
531
|
|
|
500
532
|
editor.on("update", handleUpdate);
|
|
@@ -579,7 +611,12 @@
|
|
|
579
611
|
/>
|
|
580
612
|
{/if}
|
|
581
613
|
|
|
582
|
-
|
|
614
|
+
<!--
|
|
615
|
+
본문이 들어가는 자리. **이름을 준다** — 호스트가 상자 높이를 정해 놓고 이 안을
|
|
616
|
+
채우게 하려면 이 요소를 지목할 수 있어야 한다. 이름이 없으면 남는 높이가 아무에게도
|
|
617
|
+
배분되지 않아 글자수 표시가 상자 중간에 떠 버린다.
|
|
618
|
+
-->
|
|
619
|
+
<div class="hce-editor-body" bind:this={editorElement}></div>
|
|
583
620
|
|
|
584
621
|
{#if editor && editable}
|
|
585
622
|
{#if features.has('bubble-toolbar')}
|
|
@@ -627,8 +664,7 @@
|
|
|
627
664
|
<div
|
|
628
665
|
class="flex justify-end px-4 py-2 text-xs text-muted-foreground border-t border-border"
|
|
629
666
|
>
|
|
630
|
-
{
|
|
631
|
-
{editor.storage.characterCount.words()} 단어
|
|
667
|
+
{counts.chars} 자 · {counts.words} 단어
|
|
632
668
|
</div>
|
|
633
669
|
{/if}
|
|
634
670
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AA6BtC,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AA6BtC,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAe5D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEhE,KAAK,gBAAgB,GAAI;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,wCAAwC;IACxC,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,4BAA4B;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B,CAAC;AA8nBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdfBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/PdfBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,UAAU,EAAmB,MAAM,cAAc,CAAC;AAKnE,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"PdfBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/PdfBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,UAAU,EAAmB,MAAM,cAAc,CAAC;AAKnE,eAAO,MAAM,QAAQ,sBAujBnB,CAAC"}
|
|
@@ -11,6 +11,8 @@ export const PdfBlock = TiptapNode.create({
|
|
|
11
11
|
fileId: { default: null },
|
|
12
12
|
name: { default: "PDF" },
|
|
13
13
|
width: { default: null },
|
|
14
|
+
// 헤더에 보여줄 이름. `null` 이면 `name`(파일명)을 쓴다 — 옛 문서가 그대로 동작한다.
|
|
15
|
+
label: { default: null },
|
|
14
16
|
};
|
|
15
17
|
},
|
|
16
18
|
parseHTML() {
|
|
@@ -23,6 +25,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
23
25
|
src: dom.getAttribute("data-pdf-src") || null,
|
|
24
26
|
name: dom.getAttribute("data-pdf-name") || "PDF",
|
|
25
27
|
width: dom.getAttribute("data-pdf-width") || dom.style?.width || null,
|
|
28
|
+
label: dom.getAttribute("data-pdf-label") || null,
|
|
26
29
|
}),
|
|
27
30
|
},
|
|
28
31
|
// URL 직접 방식
|
|
@@ -33,6 +36,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
33
36
|
fileId: null,
|
|
34
37
|
name: dom.getAttribute("data-pdf-name") || "PDF",
|
|
35
38
|
width: dom.getAttribute("data-pdf-width") || dom.style?.width || null,
|
|
39
|
+
label: dom.getAttribute("data-pdf-label") || null,
|
|
36
40
|
}),
|
|
37
41
|
},
|
|
38
42
|
// 레거시: <embed type="application/pdf">
|
|
@@ -41,7 +45,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
41
45
|
getAttrs: (dom) => {
|
|
42
46
|
const src = dom.getAttribute("src") || "";
|
|
43
47
|
const name = src.split("/").pop()?.replace(/[?#].*$/, "") || "PDF";
|
|
44
|
-
return { src, fileId: null, name, width: null };
|
|
48
|
+
return { src, fileId: null, name, width: null, label: null };
|
|
45
49
|
},
|
|
46
50
|
},
|
|
47
51
|
];
|
|
@@ -53,6 +57,9 @@ export const PdfBlock = TiptapNode.create({
|
|
|
53
57
|
if (HTMLAttributes.src)
|
|
54
58
|
attrs["data-pdf-src"] = HTMLAttributes.src;
|
|
55
59
|
attrs["data-pdf-name"] = HTMLAttributes.name || "PDF";
|
|
60
|
+
// ⚠️ 값이 없으면 속성을 아예 뺀다 — 옛 문서와 출력이 구분되지 않게.
|
|
61
|
+
if (HTMLAttributes.label)
|
|
62
|
+
attrs["data-pdf-label"] = HTMLAttributes.label;
|
|
56
63
|
if (HTMLAttributes.width) {
|
|
57
64
|
attrs["data-pdf-width"] = HTMLAttributes.width;
|
|
58
65
|
attrs["style"] = `width: ${HTMLAttributes.width}`;
|
|
@@ -85,7 +92,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
85
92
|
let currentNode = node;
|
|
86
93
|
let detachResize = null;
|
|
87
94
|
const dom = document.createElement("div");
|
|
88
|
-
dom.classList.add("my-
|
|
95
|
+
dom.classList.add("my-2");
|
|
89
96
|
dom.contentEditable = "false";
|
|
90
97
|
dom.setAttribute("data-type", "pdfBlock");
|
|
91
98
|
dom.setAttribute("data-drag-handle", "");
|
|
@@ -113,17 +120,166 @@ export const PdfBlock = TiptapNode.create({
|
|
|
113
120
|
}
|
|
114
121
|
// Header
|
|
115
122
|
const header = document.createElement("div");
|
|
123
|
+
// ⚠️ `justify-between` 이 아니라 spacer 로 민다. 이름칸이 넓어져도 오른쪽 버튼이
|
|
124
|
+
// 밀려나지 않는다.
|
|
116
125
|
header.className =
|
|
117
|
-
"flex items-center
|
|
126
|
+
"flex items-center gap-2 px-3 py-1 border-b border-border select-none";
|
|
118
127
|
header.style.background = "var(--muted)";
|
|
119
128
|
wrapper.appendChild(header);
|
|
120
129
|
const cleanName = (raw) => (raw || "").replace(/[?#].*$/, "").trim() || "PDF";
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
/**
|
|
131
|
+
* 헤더 표시 이름.
|
|
132
|
+
*
|
|
133
|
+
* ⚠️ **표시 전용이다.** 다운로드 파일명은 계속 `name` 을 쓴다 — 표시 이름을
|
|
134
|
+
* `1강 자료` 로 바꿨다고 확장자 없는 파일이 내려가면 안 된다.
|
|
135
|
+
*/
|
|
136
|
+
let resolvedName = null;
|
|
137
|
+
const fallbackName = () => cleanName(resolvedName ?? currentNode.attrs.name);
|
|
138
|
+
const displayName = () => {
|
|
139
|
+
const label = currentNode.attrs.label;
|
|
140
|
+
const trimmed = typeof label === "string" ? label.trim() : "";
|
|
141
|
+
return trimmed || fallbackName();
|
|
142
|
+
};
|
|
143
|
+
let nameSpan = null;
|
|
144
|
+
let nameInput = null;
|
|
145
|
+
/** 노드 속성을 되쓴다. 리사이즈(`attachResize`)와 **같은 방식**. */
|
|
146
|
+
const setAttrs = (patch) => {
|
|
147
|
+
const pos = getPos();
|
|
148
|
+
if (pos == null)
|
|
149
|
+
return;
|
|
150
|
+
editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
151
|
+
...currentNode.attrs,
|
|
152
|
+
...patch,
|
|
153
|
+
}));
|
|
154
|
+
};
|
|
155
|
+
/** 입력칸이 글자 길이만큼만 차지하게 한다(빈 상자가 넓게 보이지 않도록). */
|
|
156
|
+
const fitInput = () => {
|
|
157
|
+
if (!nameInput)
|
|
158
|
+
return;
|
|
159
|
+
nameInput.size = Math.min(40, Math.max(6, nameInput.value.length + 1));
|
|
160
|
+
};
|
|
161
|
+
/** 표시 이름을 화면에 반영한다. 입력 중일 때는 건드리지 않는다. */
|
|
162
|
+
const syncName = () => {
|
|
163
|
+
const next = displayName();
|
|
164
|
+
if (nameSpan)
|
|
165
|
+
nameSpan.textContent = next;
|
|
166
|
+
if (nameInput &&
|
|
167
|
+
document.activeElement !== nameInput &&
|
|
168
|
+
nameInput.value !== next) {
|
|
169
|
+
nameInput.value = next;
|
|
170
|
+
}
|
|
171
|
+
fitInput();
|
|
172
|
+
};
|
|
173
|
+
if (editor.isEditable) {
|
|
174
|
+
// 그 자리에서 이름을 고쳐 쓴다. 확정은 blur / Enter, 되돌리기는 Escape.
|
|
175
|
+
nameInput = document.createElement("input");
|
|
176
|
+
nameInput.type = "text";
|
|
177
|
+
nameInput.className =
|
|
178
|
+
// ⚠️ `select-text` — 헤더가 `select-none` 이라 이 칸까지 선택이 막힌다. 이름을
|
|
179
|
+
// 드래그해 고쳐 쓰려면 여기만 되돌려야 한다.
|
|
180
|
+
/*
|
|
181
|
+
* ⚠️ 평소에도 **입력칸으로 보여야 한다**(사용자 지적). 예전엔 테두리·면이 투명이라
|
|
182
|
+
* hover 하기 전에는 그냥 글자였고, 고칠 수 있다는 걸 알 방법이 없었다.
|
|
183
|
+
* 헤더 면(`--muted`) 위에 `--background` 면 + 1px 테두리를 두면 한눈에 칸으로 읽힌다.
|
|
184
|
+
*/
|
|
185
|
+
"pdf-name-input select-text text-xs text-muted-foreground min-w-0 bg-background rounded px-1.5 py-0.5 border border-border hover:border-ring focus:border-ring focus:text-foreground outline-none transition-colors";
|
|
186
|
+
nameInput.style.maxWidth = "220px";
|
|
187
|
+
nameInput.contentEditable = "false";
|
|
188
|
+
nameInput.draggable = false;
|
|
189
|
+
// ProseMirror 가 이 칸의 키/포인터를 가져가지 않도록 하는 표식(`stopEvent`).
|
|
190
|
+
nameInput.setAttribute("data-pdf-control", "");
|
|
191
|
+
nameInput.title = "표시 이름 (비우면 파일명)";
|
|
192
|
+
nameInput.setAttribute("aria-label", "PDF 표시 이름");
|
|
193
|
+
nameInput.value = displayName();
|
|
194
|
+
fitInput();
|
|
195
|
+
/** 빈칸이거나 파일명과 같으면 `label: null` — 다시 파일명을 따라간다. */
|
|
196
|
+
const commitLabel = () => {
|
|
197
|
+
if (!nameInput)
|
|
198
|
+
return;
|
|
199
|
+
const typed = nameInput.value.trim();
|
|
200
|
+
const next = typed && typed !== fallbackName() ? typed : null;
|
|
201
|
+
const current = currentNode.attrs.label ?? null;
|
|
202
|
+
if (next !== current)
|
|
203
|
+
setAttrs({ label: next });
|
|
204
|
+
nameInput.value = next || fallbackName();
|
|
205
|
+
fitInput();
|
|
206
|
+
};
|
|
207
|
+
nameInput.addEventListener("input", fitInput);
|
|
208
|
+
nameInput.addEventListener("keydown", (e) => {
|
|
209
|
+
// tiptap 이 단축키·타이핑을 가로채면 이 칸에 글자가 안 들어간다.
|
|
210
|
+
e.stopPropagation();
|
|
211
|
+
if (e.key === "Enter") {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
commitLabel();
|
|
214
|
+
nameInput?.blur();
|
|
215
|
+
}
|
|
216
|
+
else if (e.key === "Escape") {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
// 되돌린 뒤 blur — 값이 현재와 같아져 blur 의 커밋은 무시된다.
|
|
219
|
+
if (nameInput)
|
|
220
|
+
nameInput.value = displayName();
|
|
221
|
+
nameInput?.blur();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
nameInput.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
225
|
+
nameInput.addEventListener("blur", commitLabel);
|
|
226
|
+
header.appendChild(nameInput);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
nameSpan = document.createElement("span");
|
|
230
|
+
nameSpan.className =
|
|
231
|
+
"text-xs text-muted-foreground truncate select-none min-w-0";
|
|
232
|
+
nameSpan.style.maxWidth = "220px";
|
|
233
|
+
nameSpan.style.userSelect = "none";
|
|
234
|
+
nameSpan.textContent = displayName();
|
|
235
|
+
header.appendChild(nameSpan);
|
|
236
|
+
}
|
|
237
|
+
const spacer = document.createElement("div");
|
|
238
|
+
spacer.className = "flex-1";
|
|
239
|
+
header.appendChild(spacer);
|
|
240
|
+
// 너비 프리셋 (편집 가능 모드에서만)
|
|
241
|
+
const PRESET_BASE = "px-1.5 py-0.5 rounded text-xs leading-none tabular-nums transition-colors select-none";
|
|
242
|
+
const PRESET_IDLE = `${PRESET_BASE} text-muted-foreground hover:bg-background pdf-width-preset`;
|
|
243
|
+
const PRESET_ACTIVE = `${PRESET_BASE} bg-primary text-primary-foreground pdf-width-preset is-active`;
|
|
244
|
+
const presetButtons = [];
|
|
245
|
+
if (editor.isEditable) {
|
|
246
|
+
const presetGroup = document.createElement("div");
|
|
247
|
+
presetGroup.className = "flex items-center gap-0.5";
|
|
248
|
+
// ⚠️ 25% 는 뺐다 — 그 폭이면 PDF 글자를 읽을 수 없어 고를 이유가 없다(사용자 결정).
|
|
249
|
+
for (const value of ["50%", "75%", "100%"]) {
|
|
250
|
+
const btn = document.createElement("button");
|
|
251
|
+
btn.type = "button";
|
|
252
|
+
btn.className = PRESET_IDLE;
|
|
253
|
+
btn.title = `너비 ${value}`;
|
|
254
|
+
btn.textContent = value;
|
|
255
|
+
/*
|
|
256
|
+
* ⚠️ `mousedown` 에서 기본 동작을 막는다. 안 막으면 브라우저가 여기서 텍스트 선택을
|
|
257
|
+
* 시작하고, 곧바로 `setAttrs` 가 NodeView 를 다시 그리면서 그 선택이 **헤더 전체**로
|
|
258
|
+
* 번져 파일명·버튼이 통째로 파랗게 칠해졌다(사용자 지적).
|
|
259
|
+
* 클릭 자체는 `click` 에서 처리하므로 동작에는 영향이 없다.
|
|
260
|
+
*/
|
|
261
|
+
btn.addEventListener("mousedown", (e) => {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
e.stopPropagation();
|
|
264
|
+
});
|
|
265
|
+
btn.addEventListener("click", (e) => {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
setAttrs({ width: value });
|
|
269
|
+
});
|
|
270
|
+
presetGroup.appendChild(btn);
|
|
271
|
+
presetButtons.push({ value, el: btn });
|
|
272
|
+
}
|
|
273
|
+
header.appendChild(presetGroup);
|
|
274
|
+
}
|
|
275
|
+
/** 현재 `width` 와 같은 프리셋을 눌린 상태로 만든다. */
|
|
276
|
+
const syncPresets = (width) => {
|
|
277
|
+
for (const { value, el } of presetButtons) {
|
|
278
|
+
el.className = width === value ? PRESET_ACTIVE : PRESET_IDLE;
|
|
279
|
+
el.setAttribute("aria-pressed", width === value ? "true" : "false");
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
syncPresets(node.attrs.width ?? null);
|
|
127
283
|
const btnGroup = document.createElement("div");
|
|
128
284
|
btnGroup.className = "flex items-center gap-1";
|
|
129
285
|
header.appendChild(btnGroup);
|
|
@@ -261,7 +417,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
261
417
|
if (totalPages > 1) {
|
|
262
418
|
navDiv = document.createElement("div");
|
|
263
419
|
navDiv.className =
|
|
264
|
-
"flex items-center justify-center gap-4 px-
|
|
420
|
+
"flex items-center justify-center gap-4 px-3 py-1 border-t border-border";
|
|
265
421
|
navDiv.style.background = "var(--muted)";
|
|
266
422
|
const prevBtn = document.createElement("button");
|
|
267
423
|
prevBtn.type = "button";
|
|
@@ -341,8 +497,10 @@ export const PdfBlock = TiptapNode.create({
|
|
|
341
497
|
resolver(node.attrs.fileId)
|
|
342
498
|
.then((result) => {
|
|
343
499
|
if (result.name) {
|
|
344
|
-
|
|
500
|
+
// 다운로드 파일명은 항상 실제 파일명. 표시는 `label` 이 있으면 그쪽이 이긴다.
|
|
501
|
+
resolvedName = result.name;
|
|
345
502
|
downloadLink.setAttribute("download", result.name);
|
|
503
|
+
syncName();
|
|
346
504
|
}
|
|
347
505
|
})
|
|
348
506
|
.catch(() => { });
|
|
@@ -359,8 +517,15 @@ export const PdfBlock = TiptapNode.create({
|
|
|
359
517
|
dom.style.width = newWidth || "";
|
|
360
518
|
}
|
|
361
519
|
currentNode = updatedNode;
|
|
520
|
+
syncPresets(newWidth ?? null);
|
|
521
|
+
syncName();
|
|
362
522
|
return true;
|
|
363
523
|
},
|
|
524
|
+
// 이름 입력칸 위의 이벤트는 ProseMirror 가 가로채면 안 된다(CardBlock 과 같은 방식).
|
|
525
|
+
stopEvent: (event) => {
|
|
526
|
+
const target = event.target;
|
|
527
|
+
return target instanceof Element && !!target.closest("[data-pdf-control]");
|
|
528
|
+
},
|
|
364
529
|
selectNode: () => { },
|
|
365
530
|
deselectNode: () => { },
|
|
366
531
|
destroy: () => {
|
|
@@ -42,6 +42,36 @@ describe('PdfBlock extension', () => {
|
|
|
42
42
|
expect(html).toContain('data-pdf-src="https://example.com/doc.pdf"');
|
|
43
43
|
expect(html).toContain('data-pdf-name="doc.pdf"');
|
|
44
44
|
});
|
|
45
|
+
it('leaves label null and omits the attribute for legacy HTML', () => {
|
|
46
|
+
createEditor('<div data-pdf-src="https://example.com/doc.pdf" data-pdf-name="doc.pdf"></div>');
|
|
47
|
+
const pdfNode = editor
|
|
48
|
+
.getJSON()
|
|
49
|
+
.content?.find((n) => n.type === 'pdfBlock');
|
|
50
|
+
expect(pdfNode?.attrs?.label).toBeNull();
|
|
51
|
+
// 값이 없으면 속성을 아예 뺀다 — 옛 문서와 출력이 구분되지 않아야 한다.
|
|
52
|
+
expect(editor.getHTML()).not.toContain('data-pdf-label');
|
|
53
|
+
});
|
|
54
|
+
it('round-trips label through parse → render → parse', () => {
|
|
55
|
+
createEditor('<div data-pdf-src="https://example.com/doc.pdf" data-pdf-name="doc.pdf" data-pdf-label="1강 자료"></div>');
|
|
56
|
+
const html = editor.getHTML();
|
|
57
|
+
expect(html).toContain('data-pdf-label="1강 자료"');
|
|
58
|
+
// 다운로드에 쓰이는 실제 파일명은 그대로 살아 있어야 한다.
|
|
59
|
+
expect(html).toContain('data-pdf-name="doc.pdf"');
|
|
60
|
+
createEditor(html);
|
|
61
|
+
const pdfNode = editor
|
|
62
|
+
.getJSON()
|
|
63
|
+
.content?.find((n) => n.type === 'pdfBlock');
|
|
64
|
+
expect(pdfNode?.attrs?.label).toBe('1강 자료');
|
|
65
|
+
expect(pdfNode?.attrs?.name).toBe('doc.pdf');
|
|
66
|
+
});
|
|
67
|
+
it('keeps the width attribute a preset button would set', () => {
|
|
68
|
+
createEditor('<div data-pdf-src="https://example.com/doc.pdf" data-pdf-width="50%"></div>');
|
|
69
|
+
const pdfNode = editor
|
|
70
|
+
.getJSON()
|
|
71
|
+
.content?.find((n) => n.type === 'pdfBlock');
|
|
72
|
+
expect(pdfNode?.attrs?.width).toBe('50%');
|
|
73
|
+
expect(editor.getHTML()).toContain('data-pdf-width="50%"');
|
|
74
|
+
});
|
|
45
75
|
it('uses default name "PDF" when name is missing', () => {
|
|
46
76
|
createEditor('<div data-pdf-src="https://example.com/x.pdf"></div>');
|
|
47
77
|
const doc = editor.getJSON();
|
package/dist/styles/editor.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.top-full{top:100%}.z-30{z-index:30}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-px{height:1px}.w-10{width:2.5rem}.w-4{width:1rem}.w-\[360px\]{width:360px}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-px{width:1px}.min-w-\[96px\]{min-width:96px}.max-w-\[90vw\]{max-width:90vw}.flex-shrink{flex-shrink:1}.-translate-x-1\/2{--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.columns-2{-moz-columns:2;column-count:2}.columns-3{-moz-columns:3;column-count:3}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:var(--radius-md,4px)}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-xl,8px)}.rounded-md{border-radius:var(--radius-md,6px)}.rounded-xl{border-radius:var(--radius-2xl,12px)}.rounded-t-xl{border-top-left-radius:var(--radius-2xl,12px);border-top-right-radius:var(--radius-2xl,12px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-border{border-color:var(--border)}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-transparent{background-color:transparent}.bg-white\/20{background-color:hsla(0,0%,100%,.2)}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-bottom:.5rem}.pt-2,.py-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.opacity-30{opacity:.3}.shadow-lg{--tw-shadow:var(--shadow-dropdown,0 10px 15px -3px rgba(0,0,0,.1));--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:var(--shadow-dropdown,0 1px 2px 0 rgba(0,0,0,.05));--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:var(--shadow-popup,0 20px 25px -5px rgba(0,0,0,.1));--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hce-editor-wrapper{overflow:visible!important}.hce-color-divider{border-top:1px solid hsla(0,0%,100%,.15)}.bubble-toolbar-container button,.hce-editor-wrapper button,.slash-menu button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;cursor:pointer;box-sizing:border-box}.bubble-toolbar-container button:disabled,.hce-editor-wrapper button:disabled,.slash-menu button:disabled{cursor:default}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap p{margin:.5em 0!important;line-height:1.5}.tiptap p:first-child{margin-top:0!important}.tiptap p:last-child{margin-bottom:0!important}.tiptap p:empty,.tiptap p:has(>br:only-child){min-height:1.5em}.tiptap h1,.tiptap h2,.tiptap h3,.tiptap li,.tiptap p{white-space:pre-wrap}.tiptap h1{font-size:2rem;font-weight:700;margin:1.5em 0 .5em;line-height:1.25}.tiptap h2{font-size:1.5rem;font-weight:600;margin:1.2em 0 .4em;line-height:1.3}.tiptap h3{font-size:1.25rem;font-weight:600;margin:1em 0 .3em;line-height:1.35}.tiptap ul{list-style:disc}.tiptap ol,.tiptap ul{padding-left:1.5em;margin:.5em 0}.tiptap ol{list-style:decimal}.tiptap ul ul{list-style:circle}.tiptap ul ul ul{list-style:square}.tiptap ol ol{list-style:lower-alpha}.tiptap ol ol ol{list-style:lower-roman}.tiptap li{margin:.4em 0;line-height:1.7}.tiptap li>p{margin:0!important;line-height:inherit!important}.tiptap ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap ul[data-type=taskList] li{display:flex;align-items:baseline;gap:.5rem;margin:.3em 0;line-height:1.7;list-style:none;list-style-type:none}.tiptap ul[data-type=taskList] li>label{flex-shrink:0;display:flex;align-items:center;height:1.5em}.tiptap ul[data-type=taskList] li>label input[type=checkbox]{-moz-appearance:none;appearance:none;-webkit-appearance:none;width:1.1em;height:1.1em;margin:0;padding:0;cursor:pointer;background-color:var(--background,#fff);border:1.5px solid var(--border);border-radius:4px;transition:background-color .12s ease,border-color .12s ease,box-shadow .12s ease;display:inline-block;vertical-align:middle;flex-shrink:0}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:hover{border-color:var(--muted-foreground)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:focus-visible{outline:none;box-shadow:0 0 0 2px var(--background,#fff),0 0 0 4px var(--primary)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:checked{background-color:var(--primary);border-color:var(--primary);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' viewBox='0 0 16 16'%3E%3Cpath d='m3.5 8.5 3 3 6-6.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.tiptap ul[data-type=taskList] li>div{flex:1}.tiptap ul[data-type=taskList] li[data-checked=true]>div{text-decoration:line-through;opacity:.6}.tiptap blockquote{border-left:3px solid var(--border);padding:.5rem 1rem;margin:.75em 0;color:var(--muted-foreground);font-style:italic}.tiptap hr{border:none;border-top:2px solid color-mix(in srgb,var(--foreground) 38%,transparent);margin:1.5em 0}.tiptap code{background:var(--muted);border-radius:4px;padding:.15em .35em;font-size:.9em}.tiptap pre{background:#282c34;color:#abb2bf;border-radius:8px;padding:1em;margin:.75em 0;overflow-x:auto;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:1em;line-height:1.6}.tiptap pre code{background:none;padding:0;border-radius:0;color:#abb2bf;font-size:inherit}.tiptap pre code .hljs-keyword,.tiptap pre code .hljs-operator{color:#c678dd}.tiptap pre code .hljs-addition,.tiptap pre code .hljs-doctag,.tiptap pre code .hljs-regexp,.tiptap pre code .hljs-string{color:#98c379}.tiptap pre code .hljs-attr,.tiptap pre code .hljs-number,.tiptap pre code .hljs-selector-class,.tiptap pre code .hljs-template-variable,.tiptap pre code .hljs-type,.tiptap pre code .hljs-variable{color:#d19a66}.tiptap pre code .hljs-function,.tiptap pre code .hljs-section,.tiptap pre code .hljs-title,.tiptap pre code .hljs-title.class_{color:#61aeee}.tiptap pre code .hljs-params,.tiptap pre code .hljs-punctuation{color:#abb2bf}.tiptap pre code .hljs-comment,.tiptap pre code .hljs-quote{color:#5c6370;font-style:italic}.tiptap pre code .hljs-built_in,.tiptap pre code .hljs-formula{color:#e6c07b}.tiptap pre code .hljs-meta,.tiptap pre code .hljs-meta .hljs-keyword{color:#61aeee}.tiptap pre code .hljs-meta .hljs-string{color:#98c379}.tiptap pre code .hljs-attribute,.tiptap pre code .hljs-deletion,.tiptap pre code .hljs-name,.tiptap pre code .hljs-property,.tiptap pre code .hljs-selector-tag,.tiptap pre code .hljs-subst,.tiptap pre code .hljs-tag{color:#e06c75}.tiptap pre code .hljs-literal{color:#56b6c2}.tiptap pre code .hljs-selector-attr,.tiptap pre code .hljs-selector-id,.tiptap pre code .hljs-selector-pseudo{color:#61aeee}.tiptap pre code .hljs-link{color:#61aeee;text-decoration:underline}.tiptap pre code .hljs-bullet,.tiptap pre code .hljs-symbol{color:#61aeee}.tiptap pre code .hljs-emphasis{font-style:italic}.tiptap pre code .hljs-strong{font-weight:700}.tiptap img{max-width:100%;height:auto;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.tiptap [data-type=pdfBlock].ProseMirror-selectednode>div,.tiptap img{border-radius:8px}.tiptap a{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.tiptap a:hover{opacity:.8}.tiptap mark{background-color:#fef08a;border-radius:2px;padding:.05em .1em}.tiptap [data-type=details]{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;display:grid;grid-template-columns:26px 1fr;align-items:start;transition:background .12s}.tiptap [data-type=details]:hover{background:var(--muted)}.tiptap [data-type=details]>button{background:none;border:none;cursor:pointer;padding:0;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s;flex-shrink:0;margin-top:13px;margin-left:4px}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:5px solid;border-top:4px solid transparent;border-bottom:4px solid transparent;margin-left:2px}.tiptap [data-type=details]:hover>button{color:var(--foreground)}.tiptap [data-type=details].is-open>button{transform:rotate(90deg)}.tiptap [data-type=details]>div{flex:1;min-width:0}.tiptap [data-type=details] summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 0;display:block;border-bottom:1px solid transparent;transition:border-color .15s}.tiptap [data-type=details].is-open>div>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap [data-type=detailsContent]{padding:8px 8px 8px 0;border-left:none}.tiptap [data-type=detailsContent][hidden]{display:none}.tiptap details{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;transition:background .12s}.tiptap details:hover{background:var(--muted)}.tiptap details summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 26px;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-bottom:1px solid transparent;position:relative;list-style:none;transition:border-color .15s}.tiptap details[open]>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap details summary::-webkit-details-marker{display:none}.tiptap details summary::marker{content:""}.tiptap details summary:before{content:"";position:absolute;left:8px;top:50%;width:0;height:0;border-left:5px solid var(--muted-foreground);border-top:4px solid transparent;border-bottom:4px solid transparent;transform:translateY(-50%);transition:transform .2s,border-left-color .15s}.tiptap details:hover summary:before{border-left-color:var(--foreground)}.tiptap details[open]>summary:before{transform:translateY(-50%) rotate(90deg)}.tiptap details[open]>:not(summary){margin-top:8px;padding-left:26px;padding-right:8px}.tiptap table{border-collapse:collapse;margin:1em 0;table-layout:fixed;border:2px solid var(--border);max-width:100%}.tiptap .tableWrapper{overflow-x:auto;max-width:100%}.tiptap td,.tiptap th{border:1px solid var(--border);padding:.6em .8em;min-width:60px;vertical-align:middle;position:relative;box-sizing:border-box;overflow:hidden;word-break:break-word;line-height:1.5}.tiptap th{background:var(--muted);font-weight:600;text-align:left}.tiptap td{background:var(--background)}.tiptap .selectedCell:after{content:"";position:absolute;inset:0;background:var(--primary);opacity:.12;pointer-events:none;z-index:1}.tiptap .column-resize-handle{position:absolute;right:-3px;top:0;bottom:0;width:6px;background:var(--primary);opacity:0;cursor:col-resize;z-index:20;border-radius:2px;transition:opacity .15s}.tiptap table:hover .column-resize-handle{opacity:.4}.tiptap .column-resize-handle:hover{opacity:1}.tiptap.resize-cursor{cursor:col-resize}.tableWrapper{overflow-x:auto;overflow-y:hidden;margin:1em 0;max-width:100%;padding-right:4px}.tiptap table .grip-column,.tiptap table .grip-row,.tiptap table .grip-table{display:none!important}.tiptap .is-empty:before{content:attr(data-placeholder);float:left;color:var(--muted-foreground);opacity:.5;pointer-events:none;height:0}.tiptap h1.is-empty:before{font-size:2rem;font-weight:700}.tiptap h2.is-empty:before{font-size:1.5rem;font-weight:600}.tiptap h3.is-empty:before{font-size:1.25rem;font-weight:600}.ProseMirror-gapcursor{display:none;pointer-events:none;position:relative}.ProseMirror-gapcursor:after{content:"";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid var(--foreground);animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.ProseMirror-dropcursor{border-top:2px solid var(--primary);margin:-1px 0}.tiptap ::-moz-selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.tiptap ::selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.ProseMirror-noderangeselection ::-moz-selection{background:transparent}.ProseMirror-noderangeselection ::selection{background:transparent}.ProseMirror-noderangeselection *{caret-color:transparent}.ProseMirror-selectednode,.ProseMirror-selectednoderange{position:relative}.ProseMirror-selectednode:not(img):not([data-type=pdfBlock]):before,.ProseMirror-selectednoderange:not([data-type=pdfBlock]):before{content:"";position:absolute;inset:-.25rem;background-color:color-mix(in srgb,var(--primary) 10%,transparent);border:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:.5rem;pointer-events:none;z-index:-1}.bubble-toolbar-container{z-index:50}.hce-floating-plus{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--background);color:var(--muted-foreground);cursor:pointer;transition:all .15s}.hce-floating-plus:hover{color:var(--foreground);border-color:var(--foreground);background:var(--muted)}.hce-drag-handle{display:flex;align-items:center;gap:2px}.hce-drag-handle__plus{display:flex;align-items:center;justify-content:center;padding:4px;border-radius:4px;border:none;background:transparent;color:var(--muted-foreground);opacity:.5;cursor:pointer;transition:opacity .15s,background-color .15s}.hce-drag-handle__plus:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip{display:flex;align-items:center;justify-content:center;padding:2px;border-radius:4px;color:var(--muted-foreground);opacity:.5;cursor:grab;transition:opacity .15s,background-color .15s}.hce-drag-handle__grip:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip:active{cursor:grabbing}.tiptap div[data-type=columns]{display:grid;gap:16px;margin:1em 0}.tiptap div[data-type=columns][data-columns="2"]{grid-template-columns:1fr 1fr}.tiptap div[data-type=columns][data-columns="3"]{grid-template-columns:1fr 1fr 1fr}.tiptap div[data-type=column]{min-width:0;padding:4px 6px;border:1px dashed var(--border);border-radius:8px}.tiptap[contenteditable=true] div[data-type=column]:focus-within{border-color:var(--primary);border-style:solid}.tiptap div[data-type=columns].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:8px}@media (max-width:640px){.tiptap div[data-type=columns]{grid-template-columns:1fr!important}}.hce-editor-wrapper button[data-tooltip]{position:relative}.hce-editor-wrapper button[data-tooltip]:hover:after{content:attr(data-tooltip);position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;z-index:60}.hce-table-btn-wrap{position:relative;display:inline-flex}.hce-table-btn-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .15s;z-index:60}.hce-table-btn-wrap:hover .hce-table-btn-tooltip{opacity:1}.hce-card-frame,.tiptap [data-type=card]{position:relative}.hce-card-frame{box-sizing:border-box;width:100%;margin:12px 0;padding:26px 30px;display:flex;flex-direction:column;justify-content:center;gap:12px;overflow:hidden;border:1px solid hsla(0,0%,100%,.52);border-radius:8px;box-shadow:inset 0 0 0 1px hsla(0,0%,100%,.28),0 8px 24px rgba(15,23,42,.08);overflow-wrap:anywhere;isolation:isolate}.hce-card-frame:before{content:"";position:absolute;inset:0;z-index:0;background:linear-gradient(90deg,hsla(0,0%,100%,.14),hsla(0,0%,100%,.04) 48%,rgba(15,23,42,.08));pointer-events:none}.hce-card-content{position:relative;z-index:1;box-sizing:border-box;width:100%;max-height:100%;min-height:0;padding:18px 20px;display:flex;flex-direction:column;gap:10px;overflow:hidden;border:1px solid hsla(0,0%,100%,.56);border-radius:8px;background:hsla(0,0%,100%,.82);color:#111827;box-shadow:0 14px 42px rgba(15,23,42,.12),inset 0 1px 0 hsla(0,0%,100%,.52);backdrop-filter:blur(18px) saturate(1.16);-webkit-backdrop-filter:blur(18px) saturate(1.16)}@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.hce-card-content{background:hsla(0,0%,100%,.9)}}.hce-card-header{flex:0 0 auto}.hce-card-title{margin:0;font-size:1.65rem;font-weight:700;line-height:1.18;color:#111827}.hce-card-title[hidden]{display:none}.hce-card-title-input{box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.7);color:#111827;font:inherit;font-size:1.15rem;font-weight:700;outline:none}.hce-card-title-input:focus{border-color:var(--primary,#3382f2)}.hce-card-body{flex:1 1 auto;min-height:0;overflow:auto;font-size:1rem;line-height:1.55;color:#111827}.hce-card-body>p:first-child{margin-top:0}.hce-card-body>p:last-child{margin-bottom:0}.hce-card-bg-button{position:absolute;top:8px;right:8px;z-index:3;padding:4px 10px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.86);color:#111827;font-size:.8125rem;line-height:1.4;cursor:pointer}.hce-card-bg-button:hover{background:#fff}@media (max-width:640px){.hce-card-frame{padding:18px}.hce-card-content{padding:14px 16px}.hce-card-title{font-size:1.35rem}}.hce-active{background-color:color-mix(in srgb,var(--primary) 12%,transparent);color:var(--primary)}.hce-active:hover{background-color:color-mix(in srgb,var(--primary) 18%,transparent)}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{border-color:var(--primary)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:opacity-30:disabled{opacity:.3}
|
|
1
|
+
.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.top-full{top:100%}.z-30{z-index:30}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-px{height:1px}.w-10{width:2.5rem}.w-4{width:1rem}.w-\[360px\]{width:360px}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-\[96px\]{min-width:96px}.max-w-\[90vw\]{max-width:90vw}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-text{-webkit-user-select:text;-moz-user-select:text;user-select:text}.columns-2{-moz-columns:2;column-count:2}.columns-3{-moz-columns:3;column-count:3}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:var(--radius-md,4px)}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-xl,8px)}.rounded-md{border-radius:var(--radius-md,6px)}.rounded-xl{border-radius:var(--radius-2xl,12px)}.rounded-t-xl{border-top-left-radius:var(--radius-2xl,12px);border-top-right-radius:var(--radius-2xl,12px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-border{border-color:var(--border)}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-transparent{background-color:transparent}.bg-white\/20{background-color:hsla(0,0%,100%,.2)}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-bottom:.5rem}.pt-2,.py-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.opacity-30{opacity:.3}.shadow-lg{--tw-shadow:var(--shadow-dropdown,0 10px 15px -3px rgba(0,0,0,.1));--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:var(--shadow-dropdown,0 1px 2px 0 rgba(0,0,0,.05));--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:var(--shadow-popup,0 20px 25px -5px rgba(0,0,0,.1));--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hce-editor-wrapper{overflow:visible!important}.hce-color-divider{border-top:1px solid hsla(0,0%,100%,.15)}.bubble-toolbar-container button,.hce-editor-wrapper button,.slash-menu button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;cursor:pointer;box-sizing:border-box}.bubble-toolbar-container button:disabled,.hce-editor-wrapper button:disabled,.slash-menu button:disabled{cursor:default}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap p{margin:.5em 0!important;line-height:1.5}.tiptap p:first-child{margin-top:0!important}.tiptap p:last-child{margin-bottom:0!important}.tiptap p:empty,.tiptap p:has(>br:only-child){min-height:1.5em}.tiptap h1,.tiptap h2,.tiptap h3,.tiptap li,.tiptap p{white-space:pre-wrap}.tiptap h1{font-size:2rem;font-weight:700;margin:1.5em 0 .5em;line-height:1.25}.tiptap h2{font-size:1.5rem;font-weight:600;margin:1.2em 0 .4em;line-height:1.3}.tiptap h3{font-size:1.25rem;font-weight:600;margin:1em 0 .3em;line-height:1.35}.tiptap ul{list-style:disc}.tiptap ol,.tiptap ul{padding-left:1.5em;margin:.5em 0}.tiptap ol{list-style:decimal}.tiptap ul ul{list-style:circle}.tiptap ul ul ul{list-style:square}.tiptap ol ol{list-style:lower-alpha}.tiptap ol ol ol{list-style:lower-roman}.tiptap li{margin:.4em 0;line-height:1.7}.tiptap li>p{margin:0!important;line-height:inherit!important}.tiptap ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap ul[data-type=taskList] li{display:flex;align-items:baseline;gap:.5rem;margin:.3em 0;line-height:1.7;list-style:none;list-style-type:none}.tiptap ul[data-type=taskList] li>label{flex-shrink:0;display:flex;align-items:center;height:1.5em}.tiptap ul[data-type=taskList] li>label input[type=checkbox]{-moz-appearance:none;appearance:none;-webkit-appearance:none;width:1.1em;height:1.1em;margin:0;padding:0;cursor:pointer;background-color:var(--background,#fff);border:1.5px solid var(--border);border-radius:4px;transition:background-color .12s ease,border-color .12s ease,box-shadow .12s ease;display:inline-block;vertical-align:middle;flex-shrink:0}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:hover{border-color:var(--muted-foreground)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:focus-visible{outline:none;box-shadow:0 0 0 2px var(--background,#fff),0 0 0 4px var(--primary)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:checked{background-color:var(--primary);border-color:var(--primary);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' viewBox='0 0 16 16'%3E%3Cpath d='m3.5 8.5 3 3 6-6.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.tiptap ul[data-type=taskList] li>div{flex:1}.tiptap ul[data-type=taskList] li[data-checked=true]>div{text-decoration:line-through;opacity:.6}.tiptap blockquote{border-left:3px solid var(--border);padding:.5rem 1rem;margin:.75em 0;color:var(--muted-foreground);font-style:italic}.tiptap hr{border:none;border-top:2px solid color-mix(in srgb,var(--foreground) 38%,transparent);margin:1.5em 0}.tiptap code{background:var(--muted);border-radius:4px;padding:.15em .35em;font-size:.9em}.tiptap pre{background:#282c34;color:#abb2bf;border-radius:8px;padding:1em;margin:.75em 0;overflow-x:auto;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:1em;line-height:1.6}.tiptap pre code{background:none;padding:0;border-radius:0;color:#abb2bf;font-size:inherit}.tiptap pre code .hljs-keyword,.tiptap pre code .hljs-operator{color:#c678dd}.tiptap pre code .hljs-addition,.tiptap pre code .hljs-doctag,.tiptap pre code .hljs-regexp,.tiptap pre code .hljs-string{color:#98c379}.tiptap pre code .hljs-attr,.tiptap pre code .hljs-number,.tiptap pre code .hljs-selector-class,.tiptap pre code .hljs-template-variable,.tiptap pre code .hljs-type,.tiptap pre code .hljs-variable{color:#d19a66}.tiptap pre code .hljs-function,.tiptap pre code .hljs-section,.tiptap pre code .hljs-title,.tiptap pre code .hljs-title.class_{color:#61aeee}.tiptap pre code .hljs-params,.tiptap pre code .hljs-punctuation{color:#abb2bf}.tiptap pre code .hljs-comment,.tiptap pre code .hljs-quote{color:#5c6370;font-style:italic}.tiptap pre code .hljs-built_in,.tiptap pre code .hljs-formula{color:#e6c07b}.tiptap pre code .hljs-meta,.tiptap pre code .hljs-meta .hljs-keyword{color:#61aeee}.tiptap pre code .hljs-meta .hljs-string{color:#98c379}.tiptap pre code .hljs-attribute,.tiptap pre code .hljs-deletion,.tiptap pre code .hljs-name,.tiptap pre code .hljs-property,.tiptap pre code .hljs-selector-tag,.tiptap pre code .hljs-subst,.tiptap pre code .hljs-tag{color:#e06c75}.tiptap pre code .hljs-literal{color:#56b6c2}.tiptap pre code .hljs-selector-attr,.tiptap pre code .hljs-selector-id,.tiptap pre code .hljs-selector-pseudo{color:#61aeee}.tiptap pre code .hljs-link{color:#61aeee;text-decoration:underline}.tiptap pre code .hljs-bullet,.tiptap pre code .hljs-symbol{color:#61aeee}.tiptap pre code .hljs-emphasis{font-style:italic}.tiptap pre code .hljs-strong{font-weight:700}.tiptap img{max-width:100%;height:auto;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.tiptap [data-type=pdfBlock].ProseMirror-selectednode>div,.tiptap img{border-radius:8px}.tiptap a{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.tiptap a:hover{opacity:.8}.tiptap mark{background-color:#fef08a;border-radius:2px;padding:.05em .1em}.tiptap [data-type=details]{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;display:grid;grid-template-columns:26px 1fr;align-items:start;transition:background .12s}.tiptap [data-type=details]:hover{background:var(--muted)}.tiptap [data-type=details]>button{background:none;border:none;cursor:pointer;padding:0;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s;flex-shrink:0;margin-top:13px;margin-left:4px}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:5px solid;border-top:4px solid transparent;border-bottom:4px solid transparent;margin-left:2px}.tiptap [data-type=details]:hover>button{color:var(--foreground)}.tiptap [data-type=details].is-open>button{transform:rotate(90deg)}.tiptap [data-type=details]>div{flex:1;min-width:0}.tiptap [data-type=details] summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 0;display:block;border-bottom:1px solid transparent;transition:border-color .15s}.tiptap [data-type=details].is-open>div>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap [data-type=detailsContent]{padding:8px 8px 8px 0;border-left:none}.tiptap [data-type=detailsContent][hidden]{display:none}.tiptap details{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;transition:background .12s}.tiptap details:hover{background:var(--muted)}.tiptap details summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 26px;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-bottom:1px solid transparent;position:relative;list-style:none;transition:border-color .15s}.tiptap details[open]>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap details summary::-webkit-details-marker{display:none}.tiptap details summary::marker{content:""}.tiptap details summary:before{content:"";position:absolute;left:8px;top:50%;width:0;height:0;border-left:5px solid var(--muted-foreground);border-top:4px solid transparent;border-bottom:4px solid transparent;transform:translateY(-50%);transition:transform .2s,border-left-color .15s}.tiptap details:hover summary:before{border-left-color:var(--foreground)}.tiptap details[open]>summary:before{transform:translateY(-50%) rotate(90deg)}.tiptap details[open]>:not(summary){margin-top:8px;padding-left:26px;padding-right:8px}.tiptap table{border-collapse:collapse;margin:1em 0;table-layout:fixed;border:2px solid var(--border);max-width:100%}.tiptap .tableWrapper{overflow-x:auto;max-width:100%}.tiptap td,.tiptap th{border:1px solid var(--border);padding:.6em .8em;min-width:60px;vertical-align:middle;position:relative;box-sizing:border-box;overflow:hidden;word-break:break-word;line-height:1.5}.tiptap th{background:var(--muted);font-weight:600;text-align:left}.tiptap td{background:var(--background)}.tiptap .selectedCell:after{content:"";position:absolute;inset:0;background:var(--primary);opacity:.12;pointer-events:none;z-index:1}.tiptap .column-resize-handle{position:absolute;right:-3px;top:0;bottom:0;width:6px;background:var(--primary);opacity:0;cursor:col-resize;z-index:20;border-radius:2px;transition:opacity .15s}.tiptap table:hover .column-resize-handle{opacity:.4}.tiptap .column-resize-handle:hover{opacity:1}.tiptap.resize-cursor{cursor:col-resize}.tableWrapper{overflow-x:auto;overflow-y:hidden;margin:1em 0;max-width:100%;padding-right:4px}.tiptap table .grip-column,.tiptap table .grip-row,.tiptap table .grip-table{display:none!important}.tiptap .is-empty:before{content:attr(data-placeholder);float:left;color:var(--muted-foreground);opacity:.5;pointer-events:none;height:0}.tiptap h1.is-empty:before{font-size:2rem;font-weight:700}.tiptap h2.is-empty:before{font-size:1.5rem;font-weight:600}.tiptap h3.is-empty:before{font-size:1.25rem;font-weight:600}.ProseMirror-gapcursor{display:none;pointer-events:none;position:relative}.ProseMirror-gapcursor:after{content:"";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid var(--foreground);animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.ProseMirror-dropcursor{border-top:2px solid var(--primary);margin:-1px 0}.tiptap ::-moz-selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.tiptap ::selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.ProseMirror-noderangeselection ::-moz-selection{background:transparent}.ProseMirror-noderangeselection ::selection{background:transparent}.ProseMirror-noderangeselection *{caret-color:transparent}.ProseMirror-selectednode,.ProseMirror-selectednoderange{position:relative}.ProseMirror-selectednode:not(img):not([data-type=pdfBlock]):before,.ProseMirror-selectednoderange:not([data-type=pdfBlock]):before{content:"";position:absolute;inset:-.25rem;background-color:color-mix(in srgb,var(--primary) 10%,transparent);border:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:.5rem;pointer-events:none;z-index:-1}.bubble-toolbar-container{z-index:50}.hce-floating-plus{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--background);color:var(--muted-foreground);cursor:pointer;transition:all .15s}.hce-floating-plus:hover{color:var(--foreground);border-color:var(--foreground);background:var(--muted)}.hce-drag-handle{display:flex;align-items:center;gap:2px}.hce-drag-handle__plus{display:flex;align-items:center;justify-content:center;padding:4px;border-radius:4px;border:none;background:transparent;color:var(--muted-foreground);opacity:.5;cursor:pointer;transition:opacity .15s,background-color .15s}.hce-drag-handle__plus:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip{display:flex;align-items:center;justify-content:center;padding:2px;border-radius:4px;color:var(--muted-foreground);opacity:.5;cursor:grab;transition:opacity .15s,background-color .15s}.hce-drag-handle__grip:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip:active{cursor:grabbing}.tiptap div[data-type=columns]{display:grid;gap:16px;margin:1em 0}.tiptap div[data-type=columns][data-columns="2"]{grid-template-columns:1fr 1fr}.tiptap div[data-type=columns][data-columns="3"]{grid-template-columns:1fr 1fr 1fr}.tiptap div[data-type=column]{min-width:0;padding:4px 6px;border:1px dashed var(--border);border-radius:8px}.tiptap[contenteditable=true] div[data-type=column]:focus-within{border-color:var(--primary);border-style:solid}.tiptap div[data-type=columns].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:8px}@media (max-width:640px){.tiptap div[data-type=columns]{grid-template-columns:1fr!important}}.hce-editor-wrapper button[data-tooltip]{position:relative}.hce-editor-wrapper button[data-tooltip]:hover:after{content:attr(data-tooltip);position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;z-index:60}.hce-table-btn-wrap{position:relative;display:inline-flex}.hce-table-btn-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .15s;z-index:60}.hce-table-btn-wrap:hover .hce-table-btn-tooltip{opacity:1}.hce-card-frame,.tiptap [data-type=card]{position:relative}.hce-card-frame{box-sizing:border-box;width:100%;margin:12px 0;padding:26px 30px;display:flex;flex-direction:column;justify-content:center;gap:12px;overflow:hidden;border:1px solid hsla(0,0%,100%,.52);border-radius:8px;box-shadow:inset 0 0 0 1px hsla(0,0%,100%,.28),0 8px 24px rgba(15,23,42,.08);overflow-wrap:anywhere;isolation:isolate}.hce-card-frame:before{content:"";position:absolute;inset:0;z-index:0;background:linear-gradient(90deg,hsla(0,0%,100%,.14),hsla(0,0%,100%,.04) 48%,rgba(15,23,42,.08));pointer-events:none}.hce-card-content{position:relative;z-index:1;box-sizing:border-box;width:100%;max-height:100%;min-height:0;padding:18px 20px;display:flex;flex-direction:column;gap:10px;overflow:hidden;border:1px solid hsla(0,0%,100%,.56);border-radius:8px;background:hsla(0,0%,100%,.82);color:#111827;box-shadow:0 14px 42px rgba(15,23,42,.12),inset 0 1px 0 hsla(0,0%,100%,.52);backdrop-filter:blur(18px) saturate(1.16);-webkit-backdrop-filter:blur(18px) saturate(1.16)}@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.hce-card-content{background:hsla(0,0%,100%,.9)}}.hce-card-header{flex:0 0 auto}.hce-card-title{margin:0;font-size:1.65rem;font-weight:700;line-height:1.18;color:#111827}.hce-card-title[hidden]{display:none}.hce-card-title-input{box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.7);color:#111827;font:inherit;font-size:1.15rem;font-weight:700;outline:none}.hce-card-title-input:focus{border-color:var(--primary,#3382f2)}.hce-card-body{flex:1 1 auto;min-height:0;overflow:auto;font-size:1rem;line-height:1.55;color:#111827}.hce-card-body>p:first-child{margin-top:0}.hce-card-body>p:last-child{margin-bottom:0}.hce-card-bg-button{position:absolute;top:8px;right:8px;z-index:3;padding:4px 10px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.86);color:#111827;font-size:.8125rem;line-height:1.4;cursor:pointer}.hce-card-bg-button:hover{background:#fff}@media (max-width:640px){.hce-card-frame{padding:18px}.hce-card-content{padding:14px 16px}.hce-card-title{font-size:1.35rem}}.hce-active{background-color:color-mix(in srgb,var(--primary) 12%,transparent);color:var(--primary)}.hce-active:hover{background-color:color-mix(in srgb,var(--primary) 18%,transparent)}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:border-ring:hover{border-color:var(--ring)}.hover\:bg-background:hover{background-color:var(--background)}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{border-color:var(--primary)}.focus\:border-ring:focus{border-color:var(--ring)}.focus\:text-foreground:focus{color:var(--foreground)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:opacity-30:disabled{opacity:.3}
|
package/dist/types.d.ts
CHANGED
|
@@ -13,7 +13,14 @@ export type PromptHandler = (defaultValue: string) => Promise<string | null>;
|
|
|
13
13
|
export type ToolbarMode = 'minimal' | 'standard' | 'full';
|
|
14
14
|
/** 개별 툴바 기능 ID */
|
|
15
15
|
export type ToolbarFeature = 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'superscript' | 'subscript' | 'code' | 'text-color' | 'align-left' | 'align-center' | 'align-right' | 'paragraph' | 'h1' | 'h2' | 'h3' | 'bullet-list' | 'ordered-list' | 'checklist' | 'blockquote' | 'horizontal-rule' | 'toggle' | 'link' | 'image' | 'pdf' | 'file' | 'mbus' | 'card' | 'columns-2' | 'columns-3' | 'table' | 'code-block' | 'undo' | 'redo' | 'fixed-toolbar' | 'bubble-toolbar' | 'slash-menu' | 'table-menu' | 'character-count' | 'upload-overlay';
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* 모드별 기본 feature 프리셋.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ `'card'` 는 **일부러 빠져 있다.** 실제로 안 쓰여서 삽입 메뉴와 슬래시 메뉴에서
|
|
20
|
+
* 내렸다(사용자 요청). 다만 **확장 자체는 계속 등록된다** — 빼면 이미 카드가 들어간
|
|
21
|
+
* 문서가 열릴 때 노드를 못 알아보고 내용이 날아간다. 넣는 길만 닫은 것이라,
|
|
22
|
+
* 쓰고 싶은 호스트는 `features` 에 `'card'` 를 직접 넣으면 그대로 살아난다.
|
|
23
|
+
*/
|
|
17
24
|
export declare const TOOLBAR_PRESETS: Record<ToolbarMode, ToolbarFeature[]>;
|
|
18
25
|
/** features 배열 → Set 변환. features가 있으면 그걸 쓰고 없으면 toolbar 모드 프리셋 */
|
|
19
26
|
export declare function resolveFeatures(toolbar: ToolbarMode, features?: ToolbarFeature[]): Set<ToolbarFeature>;
|
|
@@ -50,6 +57,8 @@ export interface SlashCommandMenuProps {
|
|
|
50
57
|
export interface PdfViewerProps {
|
|
51
58
|
src: string;
|
|
52
59
|
fileName?: string;
|
|
60
|
+
/** 헤더에 보여줄 이름. 없으면 `fileName` 을 쓴다. */
|
|
61
|
+
label?: string;
|
|
53
62
|
}
|
|
54
63
|
export interface SlashMenuItem {
|
|
55
64
|
label: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,+BAA+B;AAC/B,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7E,YAAY;AACZ,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,kBAAkB;AAClB,MAAM,MAAM,cAAc,GAEvB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GACxD,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAEnD,YAAY,GAAG,cAAc,GAAG,aAAa,GAE7C,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAEhC,aAAa,GAAG,cAAc,GAAG,WAAW,GAC5C,YAAY,GAAG,iBAAiB,GAAG,QAAQ,GAE3C,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAEnD,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,GAElD,MAAM,GAAG,MAAM,GAEf,eAAe,GAAG,gBAAgB,GAAG,YAAY,GACjD,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,+BAA+B;AAC/B,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7E,YAAY;AACZ,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,kBAAkB;AAClB,MAAM,MAAM,cAAc,GAEvB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GACxD,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAEnD,YAAY,GAAG,cAAc,GAAG,aAAa,GAE7C,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAEhC,aAAa,GAAG,cAAc,GAAG,WAAW,GAC5C,YAAY,GAAG,iBAAiB,GAAG,QAAQ,GAE3C,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAEnD,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,GAElD,MAAM,GAAG,MAAM,GAEf,eAAe,GAAG,gBAAgB,GAAG,YAAY,GACjD,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,EAAE,CAgCjE,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,eAAe,CAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,cAAc,EAAE,GACzB,GAAG,CAAC,cAAc,CAAC,CAErB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,uCAAuC;IACvC,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,yFAAyF;IACzF,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,0DAA0D;IAC1D,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC"}
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* 모드별 기본 feature 프리셋.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ `'card'` 는 **일부러 빠져 있다.** 실제로 안 쓰여서 삽입 메뉴와 슬래시 메뉴에서
|
|
5
|
+
* 내렸다(사용자 요청). 다만 **확장 자체는 계속 등록된다** — 빼면 이미 카드가 들어간
|
|
6
|
+
* 문서가 열릴 때 노드를 못 알아보고 내용이 날아간다. 넣는 길만 닫은 것이라,
|
|
7
|
+
* 쓰고 싶은 호스트는 `features` 에 `'card'` 를 직접 넣으면 그대로 살아난다.
|
|
8
|
+
*/
|
|
2
9
|
export const TOOLBAR_PRESETS = {
|
|
3
10
|
full: [
|
|
4
11
|
'bold', 'italic', 'underline', 'strike', 'highlight',
|
|
@@ -7,7 +14,7 @@ export const TOOLBAR_PRESETS = {
|
|
|
7
14
|
'paragraph', 'h1', 'h2', 'h3',
|
|
8
15
|
'bullet-list', 'ordered-list', 'checklist',
|
|
9
16
|
'blockquote', 'horizontal-rule', 'toggle',
|
|
10
|
-
'link', 'image', 'pdf', 'file', 'mbus',
|
|
17
|
+
'link', 'image', 'pdf', 'file', 'mbus',
|
|
11
18
|
'columns-2', 'columns-3', 'table', 'code-block',
|
|
12
19
|
'undo', 'redo',
|
|
13
20
|
'fixed-toolbar', 'slash-menu',
|
|
@@ -19,7 +26,7 @@ export const TOOLBAR_PRESETS = {
|
|
|
19
26
|
'paragraph', 'h1', 'h2', 'h3',
|
|
20
27
|
'bullet-list', 'ordered-list', 'checklist',
|
|
21
28
|
'blockquote', 'horizontal-rule', 'toggle',
|
|
22
|
-
'link', 'image', 'pdf', 'file', 'mbus',
|
|
29
|
+
'link', 'image', 'pdf', 'file', 'mbus',
|
|
23
30
|
'columns-2', 'columns-3', 'table', 'code-block',
|
|
24
31
|
'fixed-toolbar', 'slash-menu', 'table-menu'
|
|
25
32
|
],
|