@teriusu/rich-editor 0.10.1 → 0.10.3

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.
@@ -88,6 +88,69 @@
88
88
  };
89
89
  },
90
90
  });
91
+
92
+ /**
93
+ * 편집 크롬(툴바 2 · 메뉴 2 · 모달 3)은 **`editable` 일 때만 그려지는데**
94
+ * 정적 import 면 읽기 전용 페이지도 통째로 받는다.
95
+ *
96
+ * 실측(정올 prod 빌드, `apps/jungol/.svelte-kit/output/client`): 이 일곱만 쓰는
97
+ * lucide 아이콘 53 개가 **270KB raw / 75KB gzip** 짜리 청크 하나로 앉아 있고,
98
+ * rich-editor 가 끌고 오는 그래프 전체는 2.06MB raw / 599KB gzip 이다. 그리고
99
+ * 그 그래프를 끌고 오는 라우트가 **176 개 중 91 개** — 문제 지문·글·댓글처럼
100
+ * 툴바가 평생 뜨지 않는 화면이 대부분이다.
101
+ *
102
+ * 그래서 **필요해질 때 한 번만** 받아 모든 인스턴스가 나눠 쓴다.
103
+ *
104
+ * ⚠️ **인스턴스마다 `import()` 를 걸지 않는다.** 모듈 자체는 캐시되니 손해가 없어
105
+ * 보이지만 `.then()` 이 인스턴스 수만큼 생긴다. 그 콜백들이 제각각 풀리면 툴바가
106
+ * 한 번에 서지 못하고 줄줄이 늘어선다 — 한 화면에 에디터가 20~27 개 서는 곳
107
+ * (정올 기출 퀴즈)에서 눈에 띈다. 소비 앱 래퍼(`ui/tiptap/TipTap.svelte`)가 같은
108
+ * 이유로 이미 모듈 스코프 공유 import 를 쓰고 있고, 그 주석에 실측이 남아 있다.
109
+ *
110
+ * ⚠️ 배럴(`index.ts`)에서 이 일곱을 **re-export 하면 안 된다.** 여기서 지연시켜도
111
+ * 배럴이 정적으로 참조하면 그래프에 그대로 남아 이 지연이 아무 효과가 없다.
112
+ * 필요하면 `@teriusu/rich-editor/toolbars` 서브패스로 가져간다.
113
+ */
114
+ /*
115
+ * ⚠️ 타입은 `typeof import(...)` 로 **원본 컴포넌트에서 그대로 가져온다.**
116
+ * `Component<any>` 로 뭉개면 마크업의 콜백 인자(`onConfirm={(url) => …}`)가
117
+ * `implicit any` 로 떨어져 타입 검사에서 걸린다. 타입 위치의 `import()` 는
118
+ * 컴파일 뒤 사라지므로 **번들에는 영향이 없다** — 지연 로드는 그대로다.
119
+ */
120
+ type EditorChrome = {
121
+ FixedToolbar: typeof import("./FixedToolbar.svelte").default;
122
+ BubbleToolbar: typeof import("./BubbleToolbar.svelte").default;
123
+ TableBubbleMenu: typeof import("./TableBubbleMenu.svelte").default;
124
+ SlashCommandMenu: typeof import("./SlashCommandMenu.svelte").default;
125
+ MediaPickerModal: typeof import("./MediaPickerModal.svelte").default;
126
+ InputModal: typeof import("./InputModal.svelte").default;
127
+ MathModal: typeof import("./MathModal.svelte").default;
128
+ };
129
+ let sharedChrome = $state<EditorChrome | null>(null);
130
+ let chromePending = false;
131
+ function loadEditorChrome() {
132
+ if (sharedChrome || chromePending) return;
133
+ chromePending = true;
134
+ Promise.all([
135
+ import("./FixedToolbar.svelte"),
136
+ import("./BubbleToolbar.svelte"),
137
+ import("./TableBubbleMenu.svelte"),
138
+ import("./SlashCommandMenu.svelte"),
139
+ import("./MediaPickerModal.svelte"),
140
+ import("./InputModal.svelte"),
141
+ import("./MathModal.svelte"),
142
+ ]).then(([fixed, bubble, table, slash, media, input, math]) => {
143
+ sharedChrome = {
144
+ FixedToolbar: fixed.default,
145
+ BubbleToolbar: bubble.default,
146
+ TableBubbleMenu: table.default,
147
+ SlashCommandMenu: slash.default,
148
+ MediaPickerModal: media.default,
149
+ InputModal: input.default,
150
+ MathModal: math.default,
151
+ };
152
+ });
153
+ }
91
154
  </script>
92
155
 
93
156
  <script lang="ts">
@@ -97,7 +160,6 @@
97
160
  import StarterKit from "@tiptap/starter-kit";
98
161
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
99
162
  import Placeholder from "@tiptap/extension-placeholder";
100
- import Image from "@tiptap/extension-image";
101
163
  import Link from "@tiptap/extension-link";
102
164
  import Underline from "@tiptap/extension-underline";
103
165
  import TextAlign from "@tiptap/extension-text-align";
@@ -128,16 +190,10 @@
128
190
  import { Indent } from "../extensions/Indent";
129
191
  import { FileAttachment } from "../extensions/FileAttachment";
130
192
  import { MbusVideo } from "../extensions/MbusVideo";
193
+ import { ResizableImage } from "../extensions/ResizableImage";
131
194
  import { VideoEmbed } from "../extensions/VideoEmbed";
132
195
  import { CardBlock } from "../extensions/CardBlock";
133
196
  import { MathInline, MathDisplay, type MathPrompt } from "../extensions/Math";
134
- import FixedToolbar from "./FixedToolbar.svelte";
135
- import BubbleToolbar from "./BubbleToolbar.svelte";
136
- import SlashCommandMenu from "./SlashCommandMenu.svelte";
137
- import TableBubbleMenu from "./TableBubbleMenu.svelte";
138
- import MathModal from "./MathModal.svelte";
139
- import MediaPickerModal from "./MediaPickerModal.svelte";
140
- import InputModal from "./InputModal.svelte";
141
197
  import type { UploadHandler, PromptHandler, ToolbarMode, ToolbarFeature } from "../types";
142
198
  import { resolveFeatures } from "../types";
143
199
  import type { FileResolver } from "../extensions/FileAttachment";
@@ -189,6 +245,25 @@
189
245
  let editorElement: HTMLDivElement | undefined = $state();
190
246
  let editor: Editor | undefined = $state();
191
247
 
248
+ /*
249
+ * 편집 크롬을 **필요해질 때** 받아 온다(사유·실측은 모듈 블록의 `loadEditorChrome` 주석).
250
+ * `$effect` 라 SSR 에서는 돌지 않는다 — 크롬은 어차피 `editor` 가 선 뒤에만 그려지고
251
+ * `editor` 는 `onMount` 에서 만들어지므로 서버에서는 그릴 일이 없다.
252
+ *
253
+ * 아래 일곱은 **원래 이름 그대로** 두어 마크업이 그대로 읽히게 한다. 도착 전에는
254
+ * `undefined` 이고, 마크업이 `sharedChrome` 유무로 이미 갈라져 있어 그때는 그리지 않는다.
255
+ */
256
+ $effect(() => {
257
+ if (editable) loadEditorChrome();
258
+ });
259
+ const FixedToolbar = $derived(sharedChrome?.FixedToolbar);
260
+ const BubbleToolbar = $derived(sharedChrome?.BubbleToolbar);
261
+ const TableBubbleMenu = $derived(sharedChrome?.TableBubbleMenu);
262
+ const SlashCommandMenu = $derived(sharedChrome?.SlashCommandMenu);
263
+ const MediaPickerModal = $derived(sharedChrome?.MediaPickerModal);
264
+ const InputModal = $derived(sharedChrome?.InputModal);
265
+ const MathModal = $derived(sharedChrome?.MathModal);
266
+
192
267
  /*
193
268
  * 글자수는 `editor.storage` 에서 읽는데 그건 `$state` 가 아니라, 그냥 부르면 첫 값에
194
269
  * 굳어 **타이핑해도 숫자가 안 바뀐다**(사용자 지적). 트랜잭션마다 카운터를 올리고
@@ -558,7 +633,11 @@
558
633
  }),
559
634
  ]
560
635
  : []),
561
- Image.configure({ inline: false }),
636
+ /*
637
+ * 기본 `Image` 대신 확장본을 쓴다 — 노드 타입은 그대로 `image` 이고
638
+ * 드래그 손잡이(`width` 속성으로 저장)만 얹은 것이다. `ResizableImage.ts` 참고.
639
+ */
640
+ ResizableImage.configure({ inline: false }),
562
641
  Link.configure({
563
642
  openOnClick: false,
564
643
  HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
@@ -799,7 +878,7 @@
799
878
  ondragover={(e) => e.preventDefault()}
800
879
  ondrop={(e) => { if (!onUploadFile) e.preventDefault(); }}
801
880
  >
802
- {#if editor && editable && features.has('fixed-toolbar')}
881
+ {#if editor && editable && FixedToolbar && features.has('fixed-toolbar')}
803
882
  <FixedToolbar
804
883
  {editor}
805
884
  {features}
@@ -821,11 +900,11 @@
821
900
  <div class="hce-editor-body" bind:this={editorElement}></div>
822
901
 
823
902
  {#if editor && editable}
824
- {#if features.has('bubble-toolbar')}
903
+ {#if BubbleToolbar && features.has('bubble-toolbar')}
825
904
  <BubbleToolbar {editor} {features} {onPromptLink} />
826
905
  {/if}
827
906
 
828
- {#if features.has('table-menu')}
907
+ {#if TableBubbleMenu && features.has('table-menu')}
829
908
  <TableBubbleMenu {editor} />
830
909
  {/if}
831
910
 
@@ -839,7 +918,7 @@
839
918
  </div>
840
919
  {/if}
841
920
 
842
- {#if features.has('slash-menu') && slashMenuOpen}
921
+ {#if SlashCommandMenu && features.has('slash-menu') && slashMenuOpen}
843
922
  <div
844
923
  style="top: {slashMenuPos.top}px; left: {slashMenuPos.left}px"
845
924
  class="fixed z-50"
@@ -872,7 +951,7 @@
872
951
  이미지·파일 고르기. 둘이 **같은 모달**을 쓰고 문구만 다르다
873
952
  (`MediaPickerModal` 주석 참고).
874
953
  -->
875
- {#if mediaPicker === 'image'}
954
+ {#if MediaPickerModal && mediaPicker === 'image'}
876
955
  <MediaPickerModal
877
956
  title="이미지 추가"
878
957
  accept="image/*"
@@ -885,7 +964,7 @@
885
964
  onCancel={() => (mediaPicker = null)}
886
965
  />
887
966
  {/if}
888
- {#if mediaPicker === 'file'}
967
+ {#if MediaPickerModal && mediaPicker === 'file'}
889
968
  <MediaPickerModal
890
969
  title="파일 추가"
891
970
  uploadLabel="파일을 선택하세요"
@@ -898,7 +977,7 @@
898
977
  {/if}
899
978
 
900
979
  <!-- 업로드를 못 하는 호스트용 폴백(URL 만 받는다). 위 `pickImage` 참고. -->
901
- {#if imageUrlPrompt}
980
+ {#if InputModal && imageUrlPrompt}
902
981
  <InputModal
903
982
  title="이미지 URL 입력"
904
983
  placeholder="https://example.com/image.png"
@@ -910,7 +989,7 @@
910
989
  />
911
990
  {/if}
912
991
 
913
- {#if mathPrompt}
992
+ {#if MathModal && mathPrompt}
914
993
  <MathModal
915
994
  latex={mathPrompt.latex}
916
995
  displayMode={mathPrompt.displayMode}
@@ -1 +1 @@
1
- {"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AA8FA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AA4BtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAWjD,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAQ9E,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,uDAAuD;IACvD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wCAAwC;IACxC,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,mDAAmD;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,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;AA0vBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AA6JA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AA2BtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAYjD,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC9E,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,uDAAuD;IACvD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wCAAwC;IACxC,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,mDAAmD;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,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;AA0wBJ,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":"FileAttachment.d.ts","sourceRoot":"","sources":["../../src/extensions/FileAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,cAAc,EAAE;YACd,iBAAiB,EAAE,CAAC,KAAK,EAAE;gBACzB,GAAG,CAAC,EAAE,MAAM,CAAC;gBACb,MAAM,CAAC,EAAE,MAAM,CAAC;gBAChB,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,KAAK,UAAU,CAAC;SAClB,CAAC;KACH;CACF;AAkFD,eAAO,MAAM,cAAc,kCA4NzB,CAAC"}
1
+ {"version":3,"file":"FileAttachment.d.ts","sourceRoot":"","sources":["../../src/extensions/FileAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,cAAc,EAAE;YACd,iBAAiB,EAAE,CAAC,KAAK,EAAE;gBACzB,GAAG,CAAC,EAAE,MAAM,CAAC;gBACb,MAAM,CAAC,EAAE,MAAM,CAAC;gBAChB,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,KAAK,UAAU,CAAC;SAClB,CAAC;KACH;CACF;AAkFD,eAAO,MAAM,cAAc,kCAyNzB,CAAC"}
@@ -183,35 +183,27 @@ export const FileAttachment = Node.create({
183
183
  nameEl.download = resolvedName;
184
184
  }
185
185
  updateHref();
186
- let downloadInProgress = false;
187
186
  function handleClick(e) {
188
- e.preventDefault();
187
+ // 상위(ProseMirror 노드 선택 등) 간섭만 막는다. 기본 동작을 죽일지는 분기별로 정한다.
189
188
  e.stopPropagation();
190
- if (downloadInProgress)
191
- return;
192
- const proxyUrl = getProxyUrl();
193
- const targetUrl = proxyUrl || resolvedSrc;
194
- if (!targetUrl)
189
+ const targetUrl = getProxyUrl() || resolvedSrc;
190
+ if (!targetUrl) {
191
+ e.preventDefault();
195
192
  return;
193
+ }
196
194
  if (isInlineable(resolvedName)) {
195
+ // 새 탭으로 "보여주는" 분기는 <a download>의 기본 동작(저장)과 정반대이므로
196
+ // 여기서만 기본 동작을 가로챈다.
197
+ e.preventDefault();
197
198
  window.open(targetUrl, "_blank");
199
+ return;
198
200
  }
199
- else {
200
- downloadInProgress = true;
201
- fetch(targetUrl)
202
- .then((res) => res.blob())
203
- .then((blob) => {
204
- const url = URL.createObjectURL(blob);
205
- const a = document.createElement("a");
206
- a.href = url;
207
- a.download = resolvedName;
208
- a.click();
209
- URL.revokeObjectURL(url);
210
- })
211
- .finally(() => {
212
- downloadInProgress = false;
213
- });
214
- }
201
+ // 다운로드 분기는 preventDefault 하지 않고 <a href download> 네이티브 동작에 맡긴다.
202
+ // 예전 방식(fetch → res.blob())은 파일 전체를 탭 메모리에 버퍼링한 뒤에야 저장을
203
+ // 시작해서, 381MB 첨부에서 클릭 후 수 분간 아무 반응이 없었다 — 진행률도 없고
204
+ // 브라우저 다운로드 목록에도 안 떴다. 네이티브 다운로드는 클릭 즉시 다운로드
205
+ // 관리자로 넘어가 디스크로 스트리밍되므로 대기도, 탭 메모리 점유도 없다.
206
+ // href/download는 updateHref()가 같은 우선순위(proxy → resolvedSrc)로 이미 세팅한다.
215
207
  }
216
208
  nameEl.addEventListener("click", handleClick);
217
209
  nameEl.addEventListener("mousedown", (e) => e.stopPropagation());
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 어떤 꼴로 들어오든 단위 없는 px 정수로 만든다. 못 읽으면 `null`.
3
+ *
4
+ * `%`·`em`·`auto` 처럼 px 로 환산할 수 없는 값은 **버린다**. 억지로 숫자를 뽑으면
5
+ * `50%` 가 `50px` 이 되어 이미지가 손톱만 해진다.
6
+ */
7
+ export declare function normalizeImageWidth(value: unknown): number | null;
8
+ export declare const ResizableImage: import("@tiptap/core").Node<import("@tiptap/extension-image").ImageOptions, any>;
9
+ export default ResizableImage;
10
+ //# sourceMappingURL=ResizableImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResizableImage.d.ts","sourceRoot":"","sources":["../../src/extensions/ResizableImage.ts"],"names":[],"mappings":"AAqCA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYjE;AAED,eAAO,MAAM,cAAc,kFAyFzB,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -0,0 +1,148 @@
1
+ import Image from "@tiptap/extension-image";
2
+ import { attachResize } from "../utils/resize";
3
+ /**
4
+ * 드래그로 폭을 조절할 수 있는 이미지.
5
+ *
6
+ * **새 노드가 아니라 `@tiptap/extension-image` 의 확장**이다. 저장본에 이미 쌓여 있는
7
+ * 이미지가 전부 `image` 노드라서, 새 타입을 만들면 옛 문서가 통째로 다른 것이 된다.
8
+ *
9
+ * ## 왜 `style` 이 아니라 `width` 속성인가
10
+ *
11
+ * 정올은 문항 목록에서 **저장 HTML 을 `{@html}` 로 먼저 그리면서** `sanitizeHtml` 을
12
+ * 태운다. 그 살균기의 `img` 허용 목록은 `src·alt·width·height` 뿐이라 **`style` 은
13
+ * 조용히 지워진다**(`utils/sanitize.ts`). 폭을 인라인 `style` 로 저장하면 정적 렌더에서는
14
+ * 원래 크기로 나왔다가 에디터가 뜨는 순간 조절한 크기로 튄다. 그래서 저장 형식은
15
+ * 단위 없는 px 숫자 `<img width="480">` 하나로 고정한다.
16
+ *
17
+ * ## 파싱은 관대하게, 쓰기는 좁게
18
+ *
19
+ * 읽을 때는 `width="480"` · `width="480px"` · `style="width:480px"` 을 모두 받는다
20
+ * (옛 문서·붙여넣기 대비). 쓸 때는 언제나 `width="480"` 한 가지다.
21
+ *
22
+ * ⚠️ **폭이 없는 문서는 `null` 로 둔다.** 열었다 저장하는 것만으로 크기가 박히면,
23
+ * 손대지 않은 이미지가 그 시점의 렌더 폭에 고정돼 반응형이 죽는다.
24
+ * `%` 값은 px 로 환산하지 않고 그냥 무시한다(=`null`) — 환산하려면 컨테이너 폭을
25
+ * 알아야 하는데 파싱 시점엔 없다.
26
+ *
27
+ * ## 왜 `Image` 내장 리사이즈(`options.resize`)를 안 쓰나
28
+ *
29
+ * TipTap v3 의 `Image` 에는 `ResizableNodeView` 기반 리사이즈가 들어 있지만,
30
+ * (1) `width`·`height` 를 **둘 다** 쓰고 (2) 손잡이 모양이 이 패키지의 PDF·영상·카드와
31
+ * 다르다. 미디어 블록끼리 조작감이 갈리지 않도록 공용 `attachResize` 로 통일한다.
32
+ */
33
+ /** 기본 최소값 240px 은 이미지엔 너무 크다 — 아이콘·뱃지 크기까지 줄일 수 있어야 한다. */
34
+ const MIN_WIDTH = 60;
35
+ /**
36
+ * 어떤 꼴로 들어오든 단위 없는 px 정수로 만든다. 못 읽으면 `null`.
37
+ *
38
+ * `%`·`em`·`auto` 처럼 px 로 환산할 수 없는 값은 **버린다**. 억지로 숫자를 뽑으면
39
+ * `50%` 가 `50px` 이 되어 이미지가 손톱만 해진다.
40
+ */
41
+ export function normalizeImageWidth(value) {
42
+ if (value == null)
43
+ return null;
44
+ if (typeof value === "number") {
45
+ return Number.isFinite(value) && value > 0 ? Math.round(value) : null;
46
+ }
47
+ const raw = String(value).trim();
48
+ if (!raw)
49
+ return null;
50
+ const m = /^(\d+(?:\.\d+)?)(px)?$/i.exec(raw);
51
+ if (!m)
52
+ return null;
53
+ const n = Number.parseFloat(m[1]);
54
+ if (!Number.isFinite(n) || n <= 0)
55
+ return null;
56
+ return Math.round(n);
57
+ }
58
+ export const ResizableImage = Image.extend({
59
+ addAttributes() {
60
+ return {
61
+ ...this.parent?.(),
62
+ width: {
63
+ default: null,
64
+ parseHTML: (element) => {
65
+ const fromAttr = normalizeImageWidth(element.getAttribute("width"));
66
+ if (fromAttr != null)
67
+ return fromAttr;
68
+ // `style="width:480px"` 로만 크기가 박힌 옛 문서.
69
+ return normalizeImageWidth(element.style?.width ?? null);
70
+ },
71
+ // ⚠️ 값이 없으면 **속성을 아예 내보내지 않는다.** 빈 `width=""` 도 안 된다 —
72
+ // 옛 문서와 출력이 구분되지 않아야 한다.
73
+ renderHTML: (attributes) => {
74
+ const w = normalizeImageWidth(attributes.width);
75
+ return w == null ? {} : { width: String(w) };
76
+ }
77
+ }
78
+ };
79
+ },
80
+ addNodeView() {
81
+ return ({ node, editor, getPos }) => {
82
+ // 리사이즈가 `setNodeMarkup` 으로 attrs 를 통째로 되쓰므로 **항상 최신 노드**여야
83
+ // 한다. 클로저의 `node` 를 쓰면 그 사이의 다른 속성 변경을 리사이즈가 되돌린다.
84
+ let currentNode = node;
85
+ let detachResize = null;
86
+ /*
87
+ * `<img>` 에는 자식을 붙일 수 없다 — 손잡이를 놓을 자리가 없다. 그래서
88
+ * `position:relative` 인 래퍼로 감싼다. 폭은 **래퍼**가 갖고 이미지는
89
+ * `width:100%` 로 따라간다(스타일은 `editor.css` 의 `.hce-image`).
90
+ */
91
+ const dom = document.createElement("div");
92
+ dom.className = "hce-image";
93
+ dom.setAttribute("data-type", "image");
94
+ dom.setAttribute("data-node-view-wrapper", "");
95
+ const img = document.createElement("img");
96
+ dom.appendChild(img);
97
+ const applyAttrs = (attrs) => {
98
+ const src = attrs.src == null ? "" : String(attrs.src);
99
+ if (img.getAttribute("src") !== src)
100
+ img.setAttribute("src", src);
101
+ img.setAttribute("alt", attrs.alt == null ? "" : String(attrs.alt));
102
+ if (attrs.title == null)
103
+ img.removeAttribute("title");
104
+ else
105
+ img.setAttribute("title", String(attrs.title));
106
+ const w = normalizeImageWidth(attrs.width);
107
+ if (w == null)
108
+ dom.style.removeProperty("width");
109
+ else
110
+ dom.style.width = `${w}px`;
111
+ };
112
+ applyAttrs(node.attrs);
113
+ if (editor.isEditable) {
114
+ detachResize = attachResize({
115
+ dom,
116
+ editor,
117
+ getPos: () => (typeof getPos === "function" ? getPos() : undefined),
118
+ getNode: () => currentNode,
119
+ axis: "x",
120
+ attr: "width",
121
+ min: MIN_WIDTH,
122
+ label: "이미지 너비 조절",
123
+ // 저장 형식은 단위 없는 숫자 — 살균기가 `style` 을 지우므로 `width` 속성으로 간다.
124
+ format: (v) => String(Math.round(v))
125
+ });
126
+ }
127
+ /*
128
+ * ⚠️ `selectNode`/`deselectNode` 를 **정의하지 않는다.** 빈 함수로라도 두는 순간
129
+ * ProseMirror 가 `ProseMirror-selectednode` 클래스를 직접 붙이지 않고 그 콜백에
130
+ * 맡겨 버려서, 선택 테두리 CSS 가 통째로 죽는다(PDF 재설계 때 실제로 밟았다).
131
+ */
132
+ return {
133
+ dom,
134
+ update: (updated) => {
135
+ if (updated.type !== currentNode.type)
136
+ return false;
137
+ applyAttrs(updated.attrs);
138
+ currentNode = updated;
139
+ return true;
140
+ },
141
+ destroy: () => {
142
+ detachResize?.();
143
+ }
144
+ };
145
+ };
146
+ }
147
+ });
148
+ export default ResizableImage;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ResizableImage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResizableImage.test.d.ts","sourceRoot":"","sources":["../../src/extensions/ResizableImage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { Editor } from "@tiptap/core";
3
+ import StarterKit from "@tiptap/starter-kit";
4
+ import { ResizableImage, normalizeImageWidth } from "./ResizableImage";
5
+ import { sanitizeHtml } from "../utils/sanitize";
6
+ let editor = null;
7
+ const make = (content) => {
8
+ editor = new Editor({
9
+ element: document.createElement("div"),
10
+ extensions: [StarterKit, ResizableImage.configure({ inline: false })],
11
+ content
12
+ });
13
+ return editor;
14
+ };
15
+ const imageAttrs = (e) => {
16
+ const node = e.getJSON().content?.find((n) => n.type === "image");
17
+ return node?.attrs ?? null;
18
+ };
19
+ afterEach(() => {
20
+ editor?.destroy();
21
+ editor = null;
22
+ });
23
+ describe("normalizeImageWidth", () => {
24
+ it("숫자·px·공백을 단위 없는 정수로 만든다", () => {
25
+ expect(normalizeImageWidth("480")).toBe(480);
26
+ expect(normalizeImageWidth("480px")).toBe(480);
27
+ expect(normalizeImageWidth(" 480px ")).toBe(480);
28
+ expect(normalizeImageWidth(480.4)).toBe(480);
29
+ });
30
+ it("px 로 환산할 수 없는 값은 버린다", () => {
31
+ // 컨테이너 폭을 모르는 시점이라 `%` 를 px 로 바꿀 수 없다.
32
+ // 억지로 숫자만 뽑으면 `50%` 가 `50px` 이 되어 이미지가 손톱만 해진다.
33
+ expect(normalizeImageWidth("50%")).toBeNull();
34
+ expect(normalizeImageWidth("20em")).toBeNull();
35
+ expect(normalizeImageWidth("auto")).toBeNull();
36
+ expect(normalizeImageWidth("")).toBeNull();
37
+ expect(normalizeImageWidth(null)).toBeNull();
38
+ expect(normalizeImageWidth(0)).toBeNull();
39
+ });
40
+ });
41
+ describe("ResizableImage", () => {
42
+ it("노드 이름은 그대로 image 다 (저장본이 전부 image 노드다)", () => {
43
+ const e = make("<p></p>");
44
+ expect(e.extensionManager.extensions.find((x) => x.name === "image")).toBeDefined();
45
+ });
46
+ /*
47
+ * 가장 중요한 케이스. **열었다 저장하는 것만으로 크기가 박히면 안 된다** —
48
+ * 손대지 않은 이미지가 그 시점의 렌더 폭에 고정되면 반응형이 죽는다.
49
+ */
50
+ it("width 없는 img 는 왕복해도 width 속성이 생기지 않는다", () => {
51
+ const e = make('<img src="https://example.com/a.png">');
52
+ expect(imageAttrs(e)?.width).toBeNull();
53
+ const html = e.getHTML();
54
+ expect(html).toContain('src="https://example.com/a.png"');
55
+ expect(html).not.toContain("width=");
56
+ });
57
+ it('width="480" 을 480 으로 읽는다', () => {
58
+ const e = make('<img src="https://example.com/a.png" width="480">');
59
+ expect(imageAttrs(e)?.width).toBe(480);
60
+ });
61
+ it('width="480px" 도 480 으로 정규화한다', () => {
62
+ const e = make('<img src="https://example.com/a.png" width="480px">');
63
+ expect(imageAttrs(e)?.width).toBe(480);
64
+ });
65
+ it('style="width:480px" 만 있는 옛 문서도 480 으로 읽는다', () => {
66
+ const e = make('<img src="https://example.com/a.png" style="width: 480px">');
67
+ expect(imageAttrs(e)?.width).toBe(480);
68
+ });
69
+ it("퍼센트 폭은 px 로 바꾸지 않고 무시한다", () => {
70
+ const e = make('<img src="https://example.com/a.png" style="width: 50%">');
71
+ expect(imageAttrs(e)?.width).toBeNull();
72
+ expect(e.getHTML()).not.toContain("width=");
73
+ });
74
+ /*
75
+ * 저장 형식이 `style` 이면 안 된다 — 정올이 저장 HTML 을 `{@html}` 로 그릴 때
76
+ * 태우는 `sanitizeHtml` 의 img 허용 목록에 `style` 이 없어서 조용히 지워진다.
77
+ * 그러면 정적 렌더는 원래 크기, 에디터는 조절한 크기로 화면이 튄다.
78
+ */
79
+ it("저장은 언제나 단위 없는 width 속성 한 가지다", () => {
80
+ const e = make('<img src="https://example.com/a.png" style="width: 480px">');
81
+ const html = e.getHTML();
82
+ expect(html).toContain('width="480"');
83
+ expect(html).not.toContain("480px");
84
+ expect(html).not.toContain("style=");
85
+ });
86
+ it("저장한 폭이 살균을 통과해도 살아남는다", () => {
87
+ const e = make('<img src="https://example.com/a.png" width="480">');
88
+ expect(sanitizeHtml(e.getHTML())).toContain('width="480"');
89
+ });
90
+ });
package/dist/index.d.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  export { default as TipTapEditor } from "./components/TipTapEditor.svelte";
2
- export { default as FixedToolbar } from "./components/FixedToolbar.svelte";
3
- export { default as BubbleToolbar } from "./components/BubbleToolbar.svelte";
4
- export { default as SlashCommandMenu } from "./components/SlashCommandMenu.svelte";
5
- export { default as InputModal } from "./components/InputModal.svelte";
6
- export { default as MathModal } from "./components/MathModal.svelte";
7
- export { default as TableBubbleMenu } from "./components/TableBubbleMenu.svelte";
8
2
  export { PdfBlock } from "./extensions/PdfBlock";
9
3
  export { Indent } from "./extensions/Indent";
10
4
  export { FixedDetails } from "./extensions/FixedDetails";
11
5
  export { FileAttachment } from "./extensions/FileAttachment";
12
6
  export type { FileResolver, FileResolveResult } from "./extensions/FileAttachment";
13
7
  export { MbusVideo } from "./extensions/MbusVideo";
8
+ export { ResizableImage, normalizeImageWidth } from "./extensions/ResizableImage";
14
9
  export { CardBlock } from "./extensions/CardBlock";
15
10
  export type { CardBackgroundPrompt, CardBlockOptions } from "./extensions/CardBlock";
16
11
  export { Columns } from "./extensions/Columns";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGjF,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAahC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG5D,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAW3E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAahC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG5D,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,17 +1,20 @@
1
1
  // 컴포넌트
2
2
  export { default as TipTapEditor } from "./components/TipTapEditor.svelte";
3
- export { default as FixedToolbar } from "./components/FixedToolbar.svelte";
4
- export { default as BubbleToolbar } from "./components/BubbleToolbar.svelte";
5
- export { default as SlashCommandMenu } from "./components/SlashCommandMenu.svelte";
6
- export { default as InputModal } from "./components/InputModal.svelte";
7
- export { default as MathModal } from "./components/MathModal.svelte";
8
- export { default as TableBubbleMenu } from "./components/TableBubbleMenu.svelte";
3
+ /*
4
+ * ⚠️ **툴바·메뉴·모달 여섯은 여기서 re-export 하지 않는다** → `@teriusu/rich-editor/toolbars`.
5
+ * `TipTapEditor` 가 그것들을 `editable` 일 때만 동적으로 받도록 해 놨는데(그쪽
6
+ * `loadEditorChrome` 주석), 배럴이 정적으로 참조하면 **지연이 통째로 무효가 된다** —
7
+ * 배럴을 가져오는 순간 다시 그래프에 들어오기 때문이다. 실측으로 그 여섯만 쓰는
8
+ * lucide 아이콘 53 개가 75KB(gzip) 짜리 청크 하나였고, 툴바가 평생 뜨지 않는
9
+ * 읽기 전용 라우트가 정올 176 개 중 91 개다.
10
+ */
9
11
  // 익스텐션
10
12
  export { PdfBlock } from "./extensions/PdfBlock";
11
13
  export { Indent } from "./extensions/Indent";
12
14
  export { FixedDetails } from "./extensions/FixedDetails";
13
15
  export { FileAttachment } from "./extensions/FileAttachment";
14
16
  export { MbusVideo } from "./extensions/MbusVideo";
17
+ export { ResizableImage, normalizeImageWidth } from "./extensions/ResizableImage";
15
18
  export { CardBlock } from "./extensions/CardBlock";
16
19
  export { Columns } from "./extensions/Columns";
17
20
  export { Column } from "./extensions/Column";
@@ -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-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}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.\!table{display:table!important}.table{display:table}.grid{display:grid}.hidden{display:none}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-px{height:1px}.min-h-\[52px\]{min-height:52px}.w-10{width:2.5rem}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-\[440px\]{width:440px}.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))}.\!transform{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))!important}.transform{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}.resize-y{resize:vertical}.resize{resize:both}.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}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-all{word-break:break-all}.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)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-popover{background-color:var(--popover)}.bg-transparent{background-color:transparent}.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}.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-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[11px\]{font-size:11px}.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}.text-destructive{color:var(--destructive)}.text-muted-foreground{color:var(--muted-foreground)}.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-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.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-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 var(--border)}.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}.bubble-toolbar-container .hce-btn-primary,.hce-editor-wrapper .hce-btn-primary,.slash-menu .hce-btn-primary{background-color:var(--primary);color:var(--primary-foreground,#fff)}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap [data-type=column]>:first-child,.tiptap [data-type=detailsContent]>:first-child,.tiptap blockquote>:first-child,.tiptap li>:first-child,.tiptap td>:first-child,.tiptap th>:first-child,.tiptap>:first-child{margin-top:0}.tiptap [data-type=column]>:last-child,.tiptap [data-type=detailsContent]>:last-child,.tiptap blockquote>:last-child,.tiptap li>:last-child,.tiptap td>:last-child,.tiptap th>:last-child,.tiptap>:last-child{margin-bottom:0}.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;border-radius:8px;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.pdf-frame{position:relative;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:8px;background:var(--muted)}.pdf-frame canvas{display:block;max-width:100%;border-radius:7px}.pdf-frame:focus{outline:1px solid var(--primary);outline-offset:2px}.pdf-overlay{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .15s ease;-moz-user-select:none;user-select:none;-webkit-user-select:none}.pdf-overlay .pdf-page-input{-moz-user-select:text;user-select:text;-webkit-user-select:text}.pdf-frame:hover .pdf-overlay,.pdf-overlay:focus-within,.tiptap[contenteditable=true] [data-type=pdfBlock].ProseMirror-selectednode .pdf-overlay{opacity:1}@media (hover:none){.pdf-overlay{opacity:1}}@media (prefers-reduced-motion:reduce){.pdf-overlay{transition:none}}.pdf-cluster{position:absolute;display:flex;align-items:center;gap:2px;padding:3px 5px;border-radius:999px;background:rgba(24,24,27,.42);backdrop-filter:blur(10px) saturate(1.1);color:#fff;pointer-events:auto;max-width:calc(100% - 12px);transition:background-color .15s ease}.pdf-cluster:hover{background:rgba(24,24,27,.62)}.pdf-cluster-end{top:6px;right:8px}.pdf-cluster-page{bottom:6px;left:50%;transform:translateX(-50%)}.pdf-nav-rail{position:sticky;top:0;height:100vh;max-height:100%;pointer-events:none}.pdf-nav{position:absolute;top:50%;transform:translateY(-50%);pointer-events:auto}.pdf-nav-prev{left:12px}.pdf-nav-next{right:12px}.pdf-overlay .pdf-ctl,.pdf-overlay .pdf-page,.pdf-overlay .pdf-preset{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border:0;background:transparent;padding:0;color:#e4e4e7;font-family:inherit;cursor:pointer;opacity:.92;transition:opacity .12s,color .12s}.pdf-overlay .pdf-ctl{width:20px;height:20px;flex:none}.pdf-overlay .pdf-ctl svg{width:14px;height:14px}.pdf-overlay .pdf-page,.pdf-overlay .pdf-preset{font-weight:500}.pdf-overlay .pdf-ctl:hover,.pdf-overlay .pdf-page:hover,.pdf-overlay .pdf-preset:hover{opacity:1;color:#fff}.pdf-overlay .pdf-ctl:disabled{opacity:.4;cursor:default}.pdf-overlay .pdf-ctl-danger:hover{opacity:1;color:#ff8f8f}.pdf-overlay .pdf-ctl:focus-visible,.pdf-overlay .pdf-page-input:focus-visible,.pdf-overlay .pdf-page:focus-visible,.pdf-overlay .pdf-preset:focus-visible{outline:2px solid hsla(0,0%,100%,.85);outline-offset:2px;border-radius:4px;opacity:1}.pdf-overlay .pdf-preset{height:18px;font-size:11px;line-height:1;font-variant-numeric:tabular-nums;flex:none}.pdf-overlay .pdf-preset.is-active,.pdf-overlay .pdf-preset.is-active:hover{opacity:1;color:#fff;font-weight:600;box-shadow:inset 0 -1px 0 0 currentColor}.pdf-overlay .pdf-fit{width:19px;height:18px;border-radius:3px}.pdf-overlay .pdf-fit svg{width:13px;height:13px}.pdf-overlay .pdf-fit.is-active,.pdf-overlay .pdf-fit.is-active:hover{opacity:1;color:#fff;box-shadow:inset 0 -1px 0 0 currentColor}.pdf-overlay [data-tip]{position:relative}.pdf-overlay [data-tip]:after{content:attr(data-tip);position:absolute;top:calc(100% + 7px);right:0;z-index:1;white-space:nowrap;font-size:11px;line-height:1.4;font-weight:400;padding:4px 8px;border-radius:5px;background:rgba(24,24,27,.94);backdrop-filter:blur(6px);color:#fff;box-shadow:0 4px 12px rgba(0,0,0,.35);opacity:0;pointer-events:none;transition:opacity .12s ease .35s}.pdf-overlay [data-tip]:focus-visible:after,.pdf-overlay [data-tip]:hover:after{opacity:1}.pdf-cluster-page [data-tip]:after{top:auto;bottom:calc(100% + 7px)}.pdf-nav-prev[data-tip]:after{right:auto;left:0}.pdf-overlay .pdf-page{height:16px;font-size:10px;line-height:1;font-variant-numeric:tabular-nums}.pdf-cluster-page{padding:2px 6px}.pdf-overlay .pdf-nav{width:30px;height:30px;padding:0;background:rgba(24,24,27,.42)}.pdf-overlay .pdf-nav:hover{background:rgba(24,24,27,.62)}.pdf-overlay .pdf-nav svg{width:18px;height:18px}.pdf-overlay .pdf-nav:disabled{opacity:.4}.pdf-divider{flex:none;width:1px;height:11px;margin:0 3px;background:hsla(0,0%,100%,.3)}.pdf-overlay .pdf-page-input{box-sizing:border-box;height:20px;width:46px;padding:0 5px;font-size:12px;text-align:center;font-variant-numeric:tabular-nums;background:rgba(24,24,27,.85);border:1px solid hsla(0,0%,100%,.5);color:#fff;border-radius:4px;font-family:inherit;outline:none}.pdf-status{display:flex;align-items:center;justify-content:center;gap:8px;padding:2.5rem 1rem;font-size:.875rem;color:var(--muted-foreground);text-align:center}.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:30px 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;font:inherit;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s,background-color .15s;flex-shrink:0;margin-top:calc(-2px + 1lh/2);margin-left:4px}.tiptap [data-type=details]>button:hover{background:color-mix(in srgb,var(--foreground) 8%,transparent)}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:clamp(6px,.42em,11px) solid;border-top:clamp(4.7px,.33em,8.6px) solid transparent;border-bottom:clamp(4.7px,.33em,8.6px) 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=details] summary[data-level="1"],.tiptap details summary[data-level="1"]{font-size:2rem;font-weight:700;line-height:1.25}.tiptap [data-type=details] summary[data-level="2"],.tiptap details summary[data-level="2"]{font-size:1.5rem;font-weight:600;line-height:1.3}.tiptap [data-type=details] summary[data-level="3"],.tiptap details summary[data-level="3"]{font-size:1.25rem;font-weight:600;line-height:1.35}.tiptap [data-type=details]:has(>div>summary[data-level="1"])>button{font-size:2rem;line-height:1.25}.tiptap [data-type=details]:has(>div>summary[data-level="2"])>button{font-size:1.5rem;line-height:1.3}.tiptap [data-type=details]:has(>div>summary[data-level="3"])>button{font-size:1.25rem;line-height:1.35}.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:25px;top:calc(10px + .5lh);width:0;height:0;border-left:clamp(6px,.42em,11px) solid var(--muted-foreground);border-top:clamp(4.7px,.33em,8.6px) solid transparent;border-bottom:clamp(4.7px,.33em,8.6px) 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:-1px;bottom:-1px;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]):not([data-type=mbusVideo]):not([data-type=videoEmbed]):before,.ProseMirror-selectednoderange:not([data-type=pdfBlock]):not([data-type=mbusVideo]):not([data-type=videoEmbed]):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}.tiptap[contenteditable=true] [data-type=mbusVideo].ProseMirror-selectednode,.tiptap[contenteditable=true] [data-type=pdfBlock].ProseMirror-selectednode,.tiptap[contenteditable=true] [data-type=videoEmbed].ProseMirror-selectednode,.tiptap[contenteditable=true] img.ProseMirror-selectednode{outline:1px solid var(--primary);outline-offset:2px;border-radius:8px}.hce-table-bubble{z-index:var(--hce-menu-z,100)}.bubble-toolbar-container{position:absolute;top:0;left:0;width:-moz-max-content;width:max-content;z-index:calc(var(--hce-menu-z, 100) + 1)}.hce-toolbar:has(.absolute)~.bubble-toolbar-container,.hce-toolbar:has(.absolute)~.hce-table-bubble{display:none}.hce-menu-surface{background-color:var(--hce-menu-surface,#333)}.hce-floating-panel{background-color:color-mix(in srgb,var(--foreground) 8%,var(--popover,var(--background,#fff)));border:1px solid color-mix(in srgb,var(--foreground) 18%,var(--border));box-shadow:0 8px 24px color-mix(in srgb,var(--foreground) 16%,transparent),0 2px 6px color-mix(in srgb,var(--foreground) 10%,transparent)}.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}}[data-type=tabs]{margin:1em 0;border:1px solid var(--border);border-radius:10px;overflow:hidden}[data-type=tabs].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent)}.hce-tabs-bar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 6px;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--muted) 55%,transparent);-webkit-user-select:none;-moz-user-select:none;user-select:none}.hce-tab-chip{position:relative;display:inline-flex;align-items:center;gap:2px;padding-right:2px;border-radius:6px;color:var(--muted-foreground);touch-action:none}.hce-tab-chip:hover{background:color-mix(in srgb,var(--primary) 8%,transparent)}.hce-tab-chip.is-active{background:color-mix(in srgb,var(--primary) 14%,transparent);color:var(--primary)}.hce-tab-chip-label{max-width:14rem;padding:3px 8px;border:0;background:transparent;color:inherit;font:inherit;font-size:.8125rem;font-weight:500;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}.hce-tab-chip.is-active .hce-tab-chip-label{font-weight:600}.hce-tab-chip-close{display:inline-flex;visibility:hidden;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;border-radius:4px;background:transparent;color:inherit;font-size:13px;line-height:1;cursor:pointer}.hce-tab-chip:hover .hce-tab-chip-close:not(.is-disabled){visibility:visible}.hce-tab-chip-close.is-disabled{cursor:default;pointer-events:none}.hce-tab-chip-close:hover{background:color-mix(in srgb,var(--foreground) 12%,transparent)}.hce-tabs-add{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--muted-foreground);font-size:15px;line-height:1;cursor:pointer}.hce-tabs-add:hover{background:color-mix(in srgb,var(--primary) 10%,transparent);color:var(--primary)}.hce-tabs-bar.is-dragging{cursor:grabbing}.hce-tab-chip.is-dragged{opacity:.45}.hce-tabs-bar.is-dragging .hce-tab-chip:hover .hce-tab-chip-close{visibility:hidden}.hce-tab-chip.is-drop-after:after,.hce-tab-chip.is-drop-before:before{content:"";position:absolute;top:2px;bottom:2px;width:2px;border-radius:1px;background:var(--primary)}.hce-tab-chip.is-drop-before:before{left:-3px}.hce-tab-chip.is-drop-after:after{right:-3px}.hce-tab-title-input{box-sizing:border-box;width:8rem;margin:2px;padding:2px 6px;border:1px solid var(--primary);border-radius:4px;background:var(--background);color:var(--foreground);font:inherit;font-size:.8125rem;outline:none}.hce-tabs-panels{padding:10px 12px}.hce-tabs-panels>[data-type=tab]{display:none}.hce-tabs-panels>[data-type=tab].hce-tab-active{display:block}[data-type=tabs]:not(.hce-tabs-live)>[data-type=tab]{display:none}[data-type=tabs]:not(.hce-tabs-live)>[data-type=tab]:first-child{display:block;padding:10px 12px}[data-type=tabs]:not(.hce-tabs-live):before{content:"";display:block;height:2rem;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--muted) 55%,transparent)}[data-type=tab]>p:only-child:empty:before,[data-type=tab]>p:only-child:has(>br:only-child):before{content:"빈 탭입니다. 내부를 클릭하거나 블록을 넣으세요.";float:left;height:0;color:var(--muted-foreground);opacity:.5;pointer-events:none}.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-table-btn-wrap.hce-tooltip-off:hover .hce-table-btn-tooltip{opacity:0}.tiptap math-inline,math-inline.math-node{display:inline-block;vertical-align:baseline}.tiptap math-display,math-display.math-node{display:block;margin:.9em 0;text-align:center}.math-node,.tiptap .math-node{font-size:.95em}.tiptap math-inline.math-node .katex,math-inline.math-node .katex{font-size:1.0285em}.tiptap[contenteditable=true] .math-node{cursor:pointer;border-radius:4px;transition:background-color .12s}.tiptap[contenteditable=true] .math-node:hover{background-color:color-mix(in srgb,var(--primary) 10%,transparent)}.tiptap .math-node.ProseMirror-selectednode{background-color:color-mix(in srgb,var(--primary) 18%,transparent);outline:1px solid color-mix(in srgb,var(--primary) 45%,transparent)}.tiptap .math-node.empty-math .math-placeholder{font-size:.85em;padding:0 .35em;color:var(--muted-foreground);border:1px dashed var(--border);border-radius:4px}.math-node .math-error,.tiptap .math-error{color:#dc2626;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}.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\: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\: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)}
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}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.\!table{display:table!important}.table{display:table}.grid{display:grid}.hidden{display:none}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-px{height:1px}.min-h-\[52px\]{min-height:52px}.w-10{width:2.5rem}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-\[440px\]{width:440px}.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))}.\!transform{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))!important}.transform{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}.resize-y{resize:vertical}.resize{resize:both}.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}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-all{word-break:break-all}.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)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-popover{background-color:var(--popover)}.bg-transparent{background-color:transparent}.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}.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-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[11px\]{font-size:11px}.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}.text-destructive{color:var(--destructive)}.text-muted-foreground{color:var(--muted-foreground)}.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-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.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-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 var(--border)}.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}.bubble-toolbar-container .hce-btn-primary,.hce-editor-wrapper .hce-btn-primary,.slash-menu .hce-btn-primary{background-color:var(--primary);color:var(--primary-foreground,#fff)}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap [data-type=column]>:first-child,.tiptap [data-type=detailsContent]>:first-child,.tiptap blockquote>:first-child,.tiptap li>:first-child,.tiptap td>:first-child,.tiptap th>:first-child,.tiptap>:first-child{margin-top:0}.tiptap [data-type=column]>:last-child,.tiptap [data-type=detailsContent]>:last-child,.tiptap blockquote>:last-child,.tiptap li>:last-child,.tiptap td>:last-child,.tiptap th>:last-child,.tiptap>:last-child{margin-bottom:0}.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;border-radius:8px;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.tiptap .hce-image{position:relative;display:block;width:-moz-fit-content;width:fit-content;max-width:100%;margin:.75em 0;box-sizing:border-box}.tiptap .hce-image img{display:block;width:100%;height:auto;margin:0}.pdf-frame{position:relative;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:8px;background:var(--muted)}.pdf-frame canvas{display:block;max-width:100%;border-radius:7px}.pdf-frame:focus{outline:1px solid var(--primary);outline-offset:2px}.pdf-overlay{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .15s ease;-moz-user-select:none;user-select:none;-webkit-user-select:none}.pdf-overlay .pdf-page-input{-moz-user-select:text;user-select:text;-webkit-user-select:text}.pdf-frame:hover .pdf-overlay,.pdf-overlay:focus-within,.tiptap[contenteditable=true] [data-type=pdfBlock].ProseMirror-selectednode .pdf-overlay{opacity:1}@media (hover:none){.pdf-overlay{opacity:1}}@media (prefers-reduced-motion:reduce){.pdf-overlay{transition:none}}.pdf-cluster{position:absolute;display:flex;align-items:center;gap:2px;padding:3px 5px;border-radius:999px;background:rgba(24,24,27,.42);backdrop-filter:blur(10px) saturate(1.1);color:#fff;pointer-events:auto;max-width:calc(100% - 12px);transition:background-color .15s ease}.pdf-cluster:hover{background:rgba(24,24,27,.62)}.pdf-cluster-end{top:6px;right:8px}.pdf-cluster-page{bottom:6px;left:50%;transform:translateX(-50%)}.pdf-nav-rail{position:sticky;top:0;height:100vh;max-height:100%;pointer-events:none}.pdf-nav{position:absolute;top:50%;transform:translateY(-50%);pointer-events:auto}.pdf-nav-prev{left:12px}.pdf-nav-next{right:12px}.pdf-overlay .pdf-ctl,.pdf-overlay .pdf-page,.pdf-overlay .pdf-preset{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border:0;background:transparent;padding:0;color:#e4e4e7;font-family:inherit;cursor:pointer;opacity:.92;transition:opacity .12s,color .12s}.pdf-overlay .pdf-ctl{width:20px;height:20px;flex:none}.pdf-overlay .pdf-ctl svg{width:14px;height:14px}.pdf-overlay .pdf-page,.pdf-overlay .pdf-preset{font-weight:500}.pdf-overlay .pdf-ctl:hover,.pdf-overlay .pdf-page:hover,.pdf-overlay .pdf-preset:hover{opacity:1;color:#fff}.pdf-overlay .pdf-ctl:disabled{opacity:.4;cursor:default}.pdf-overlay .pdf-ctl-danger:hover{opacity:1;color:#ff8f8f}.pdf-overlay .pdf-ctl:focus-visible,.pdf-overlay .pdf-page-input:focus-visible,.pdf-overlay .pdf-page:focus-visible,.pdf-overlay .pdf-preset:focus-visible{outline:2px solid hsla(0,0%,100%,.85);outline-offset:2px;border-radius:4px;opacity:1}.pdf-overlay .pdf-preset{height:18px;font-size:11px;line-height:1;font-variant-numeric:tabular-nums;flex:none}.pdf-overlay .pdf-preset.is-active,.pdf-overlay .pdf-preset.is-active:hover{opacity:1;color:#fff;font-weight:600;box-shadow:inset 0 -1px 0 0 currentColor}.pdf-overlay .pdf-fit{width:19px;height:18px;border-radius:3px}.pdf-overlay .pdf-fit svg{width:13px;height:13px}.pdf-overlay .pdf-fit.is-active,.pdf-overlay .pdf-fit.is-active:hover{opacity:1;color:#fff;box-shadow:inset 0 -1px 0 0 currentColor}.pdf-overlay [data-tip]{position:relative}.pdf-overlay [data-tip]:after{content:attr(data-tip);position:absolute;top:calc(100% + 7px);right:0;z-index:1;white-space:nowrap;font-size:11px;line-height:1.4;font-weight:400;padding:4px 8px;border-radius:5px;background:rgba(24,24,27,.94);backdrop-filter:blur(6px);color:#fff;box-shadow:0 4px 12px rgba(0,0,0,.35);opacity:0;pointer-events:none;transition:opacity .12s ease .35s}.pdf-overlay [data-tip]:focus-visible:after,.pdf-overlay [data-tip]:hover:after{opacity:1}.pdf-cluster-page [data-tip]:after{top:auto;bottom:calc(100% + 7px)}.pdf-nav-prev[data-tip]:after{right:auto;left:0}.pdf-overlay .pdf-page{height:16px;font-size:10px;line-height:1;font-variant-numeric:tabular-nums}.pdf-cluster-page{padding:2px 6px}.pdf-overlay .pdf-nav{width:30px;height:30px;padding:0;background:rgba(24,24,27,.42)}.pdf-overlay .pdf-nav:hover{background:rgba(24,24,27,.62)}.pdf-overlay .pdf-nav svg{width:18px;height:18px}.pdf-overlay .pdf-nav:disabled{opacity:.4}.pdf-divider{flex:none;width:1px;height:11px;margin:0 3px;background:hsla(0,0%,100%,.3)}.pdf-overlay .pdf-page-input{box-sizing:border-box;height:20px;width:46px;padding:0 5px;font-size:12px;text-align:center;font-variant-numeric:tabular-nums;background:rgba(24,24,27,.85);border:1px solid hsla(0,0%,100%,.5);color:#fff;border-radius:4px;font-family:inherit;outline:none}.pdf-status{display:flex;align-items:center;justify-content:center;gap:8px;padding:2.5rem 1rem;font-size:.875rem;color:var(--muted-foreground);text-align:center}.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:30px 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;font:inherit;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s,background-color .15s;flex-shrink:0;margin-top:calc(-2px + 1lh/2);margin-left:4px}.tiptap [data-type=details]>button:hover{background:color-mix(in srgb,var(--foreground) 8%,transparent)}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:clamp(6px,.42em,11px) solid;border-top:clamp(4.7px,.33em,8.6px) solid transparent;border-bottom:clamp(4.7px,.33em,8.6px) 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=details] summary[data-level="1"],.tiptap details summary[data-level="1"]{font-size:2rem;font-weight:700;line-height:1.25}.tiptap [data-type=details] summary[data-level="2"],.tiptap details summary[data-level="2"]{font-size:1.5rem;font-weight:600;line-height:1.3}.tiptap [data-type=details] summary[data-level="3"],.tiptap details summary[data-level="3"]{font-size:1.25rem;font-weight:600;line-height:1.35}.tiptap [data-type=details]:has(>div>summary[data-level="1"])>button{font-size:2rem;line-height:1.25}.tiptap [data-type=details]:has(>div>summary[data-level="2"])>button{font-size:1.5rem;line-height:1.3}.tiptap [data-type=details]:has(>div>summary[data-level="3"])>button{font-size:1.25rem;line-height:1.35}.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:25px;top:calc(10px + .5lh);width:0;height:0;border-left:clamp(6px,.42em,11px) solid var(--muted-foreground);border-top:clamp(4.7px,.33em,8.6px) solid transparent;border-bottom:clamp(4.7px,.33em,8.6px) 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:-1px;bottom:-1px;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=image]):not([data-type=pdfBlock]):not([data-type=mbusVideo]):not([data-type=videoEmbed]):before,.ProseMirror-selectednoderange:not([data-type=image]):not([data-type=pdfBlock]):not([data-type=mbusVideo]):not([data-type=videoEmbed]):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}.tiptap[contenteditable=true] [data-type=image].ProseMirror-selectednode,.tiptap[contenteditable=true] [data-type=mbusVideo].ProseMirror-selectednode,.tiptap[contenteditable=true] [data-type=pdfBlock].ProseMirror-selectednode,.tiptap[contenteditable=true] [data-type=videoEmbed].ProseMirror-selectednode,.tiptap[contenteditable=true] img.ProseMirror-selectednode{outline:1px solid var(--primary);outline-offset:2px;border-radius:8px}.hce-table-bubble{z-index:var(--hce-menu-z,100)}.bubble-toolbar-container{position:absolute;top:0;left:0;width:-moz-max-content;width:max-content;z-index:calc(var(--hce-menu-z, 100) + 1)}.hce-toolbar:has(.absolute)~.bubble-toolbar-container,.hce-toolbar:has(.absolute)~.hce-table-bubble{display:none}.hce-menu-surface{background-color:var(--hce-menu-surface,#333)}.hce-floating-panel{background-color:color-mix(in srgb,var(--foreground) 8%,var(--popover,var(--background,#fff)));border:1px solid color-mix(in srgb,var(--foreground) 18%,var(--border));box-shadow:0 8px 24px color-mix(in srgb,var(--foreground) 16%,transparent),0 2px 6px color-mix(in srgb,var(--foreground) 10%,transparent)}.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}}[data-type=tabs]{margin:1em 0;border:1px solid var(--border);border-radius:10px;overflow:hidden}[data-type=tabs].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent)}.hce-tabs-bar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 6px;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--muted) 55%,transparent);-webkit-user-select:none;-moz-user-select:none;user-select:none}.hce-tab-chip{position:relative;display:inline-flex;align-items:center;gap:2px;padding-right:2px;border-radius:6px;color:var(--muted-foreground);touch-action:none}.hce-tab-chip:hover{background:color-mix(in srgb,var(--primary) 8%,transparent)}.hce-tab-chip.is-active{background:color-mix(in srgb,var(--primary) 14%,transparent);color:var(--primary)}.hce-tab-chip-label{max-width:14rem;padding:3px 8px;border:0;background:transparent;color:inherit;font:inherit;font-size:.8125rem;font-weight:500;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}.hce-tab-chip.is-active .hce-tab-chip-label{font-weight:600}.hce-tab-chip-close{display:inline-flex;visibility:hidden;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;border-radius:4px;background:transparent;color:inherit;font-size:13px;line-height:1;cursor:pointer}.hce-tab-chip:hover .hce-tab-chip-close:not(.is-disabled){visibility:visible}.hce-tab-chip-close.is-disabled{cursor:default;pointer-events:none}.hce-tab-chip-close:hover{background:color-mix(in srgb,var(--foreground) 12%,transparent)}.hce-tabs-add{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--muted-foreground);font-size:15px;line-height:1;cursor:pointer}.hce-tabs-add:hover{background:color-mix(in srgb,var(--primary) 10%,transparent);color:var(--primary)}.hce-tabs-bar.is-dragging{cursor:grabbing}.hce-tab-chip.is-dragged{opacity:.45}.hce-tabs-bar.is-dragging .hce-tab-chip:hover .hce-tab-chip-close{visibility:hidden}.hce-tab-chip.is-drop-after:after,.hce-tab-chip.is-drop-before:before{content:"";position:absolute;top:2px;bottom:2px;width:2px;border-radius:1px;background:var(--primary)}.hce-tab-chip.is-drop-before:before{left:-3px}.hce-tab-chip.is-drop-after:after{right:-3px}.hce-tab-title-input{box-sizing:border-box;width:8rem;margin:2px;padding:2px 6px;border:1px solid var(--primary);border-radius:4px;background:var(--background);color:var(--foreground);font:inherit;font-size:.8125rem;outline:none}.hce-tabs-panels{padding:10px 12px}.hce-tabs-panels>[data-type=tab]{display:none}.hce-tabs-panels>[data-type=tab].hce-tab-active{display:block}[data-type=tabs]:not(.hce-tabs-live)>[data-type=tab]{display:none}[data-type=tabs]:not(.hce-tabs-live)>[data-type=tab]:first-child{display:block;padding:10px 12px}[data-type=tabs]:not(.hce-tabs-live):before{content:"";display:block;height:2rem;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--muted) 55%,transparent)}[data-type=tab]>p:only-child:empty:before,[data-type=tab]>p:only-child:has(>br:only-child):before{content:"빈 탭입니다. 내부를 클릭하거나 블록을 넣으세요.";float:left;height:0;color:var(--muted-foreground);opacity:.5;pointer-events:none}.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-table-btn-wrap.hce-tooltip-off:hover .hce-table-btn-tooltip{opacity:0}.tiptap math-inline,math-inline.math-node{display:inline-block;vertical-align:baseline}.tiptap math-display,math-display.math-node{display:block;margin:.9em 0;text-align:center}.math-node,.tiptap .math-node{font-size:.95em}.tiptap math-inline.math-node .katex,math-inline.math-node .katex{font-size:1.0285em}.tiptap[contenteditable=true] .math-node{cursor:pointer;border-radius:4px;transition:background-color .12s}.tiptap[contenteditable=true] .math-node:hover{background-color:color-mix(in srgb,var(--primary) 10%,transparent)}.tiptap .math-node.ProseMirror-selectednode{background-color:color-mix(in srgb,var(--primary) 18%,transparent);outline:1px solid color-mix(in srgb,var(--primary) 45%,transparent)}.tiptap .math-node.empty-math .math-placeholder{font-size:.85em;padding:0 .35em;color:var(--muted-foreground);border:1px dashed var(--border);border-radius:4px}.math-node .math-error,.tiptap .math-error{color:#dc2626;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}.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\: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\: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)}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 편집 크롬(툴바·메뉴·모달) 진입점.
3
+ *
4
+ * **배럴(`index.ts`)이 아니라 여기에 있는 이유**: `TipTapEditor` 는 이 일곱을
5
+ * `editable` 일 때만 동적으로 받는다(그쪽 `loadEditorChrome` 주석에 실측이 있다).
6
+ * 배럴이 같은 것을 정적으로 re-export 하면 **그 지연이 무효가 된다** — 배럴을
7
+ * 한 번이라도 가져오는 순간 그래프에 다시 들어오기 때문이다. 그래서 갈라 둔다.
8
+ *
9
+ * 직접 쓸 일은 드물다. `TipTapEditor` 를 쓰면 알아서 실린다.
10
+ * 툴바만 따로 배치하는 호스트만 `@teriusu/rich-editor/toolbars` 로 가져간다.
11
+ */
12
+ export { default as FixedToolbar } from "./components/FixedToolbar.svelte";
13
+ export { default as BubbleToolbar } from "./components/BubbleToolbar.svelte";
14
+ export { default as SlashCommandMenu } from "./components/SlashCommandMenu.svelte";
15
+ export { default as TableBubbleMenu } from "./components/TableBubbleMenu.svelte";
16
+ export { default as InputModal } from "./components/InputModal.svelte";
17
+ export { default as MathModal } from "./components/MathModal.svelte";
18
+ //# sourceMappingURL=toolbars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbars.d.ts","sourceRoot":"","sources":["../src/toolbars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 편집 크롬(툴바·메뉴·모달) 진입점.
3
+ *
4
+ * **배럴(`index.ts`)이 아니라 여기에 있는 이유**: `TipTapEditor` 는 이 일곱을
5
+ * `editable` 일 때만 동적으로 받는다(그쪽 `loadEditorChrome` 주석에 실측이 있다).
6
+ * 배럴이 같은 것을 정적으로 re-export 하면 **그 지연이 무효가 된다** — 배럴을
7
+ * 한 번이라도 가져오는 순간 그래프에 다시 들어오기 때문이다. 그래서 갈라 둔다.
8
+ *
9
+ * 직접 쓸 일은 드물다. `TipTapEditor` 를 쓰면 알아서 실린다.
10
+ * 툴바만 따로 배치하는 호스트만 `@teriusu/rich-editor/toolbars` 로 가져간다.
11
+ */
12
+ export { default as FixedToolbar } from "./components/FixedToolbar.svelte";
13
+ export { default as BubbleToolbar } from "./components/BubbleToolbar.svelte";
14
+ export { default as SlashCommandMenu } from "./components/SlashCommandMenu.svelte";
15
+ export { default as TableBubbleMenu } from "./components/TableBubbleMenu.svelte";
16
+ export { default as InputModal } from "./components/InputModal.svelte";
17
+ export { default as MathModal } from "./components/MathModal.svelte";
@@ -141,6 +141,17 @@ describe('sanitizeHtml', () => {
141
141
  expect(result).toContain('src=');
142
142
  expect(result).toContain('alt=');
143
143
  });
144
+ /*
145
+ * 이미지 리사이즈(`ResizableImage`)가 폭을 **`width` 속성**으로 저장하는 근거다.
146
+ * 인라인 `style` 로 저장하면 여기서 조용히 지워져, 정적 렌더(`{@html}` + 살균)에서는
147
+ * 원래 크기로 나왔다가 에디터가 뜨는 순간 조절한 크기로 화면이 튄다.
148
+ */
149
+ it('keeps img width but drops inline style', () => {
150
+ const input = '<img src="https://example.com/img.png" width="480" style="width: 480px">';
151
+ const result = sanitizeHtml(input);
152
+ expect(result).toContain('width="480"');
153
+ expect(result).not.toContain('style=');
154
+ });
144
155
  it('preserves data-pdf-src on div', () => {
145
156
  const input = '<div data-pdf-src="https://example.com/file.pdf" data-pdf-name="file.pdf"></div>';
146
157
  const result = sanitizeHtml(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teriusu/rich-editor",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "TipTap 기반 Notion-style 리치 텍스트 에디터 (Svelte)",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -11,6 +11,11 @@
11
11
  "svelte": "./dist/index.js",
12
12
  "default": "./dist/index.js"
13
13
  },
14
+ "./toolbars": {
15
+ "types": "./dist/toolbars.d.ts",
16
+ "svelte": "./dist/toolbars.js",
17
+ "default": "./dist/toolbars.js"
18
+ },
14
19
  "./lowlight": {
15
20
  "types": "./dist/utils/lowlight.d.ts",
16
21
  "default": "./dist/utils/lowlight.js"