@teriusu/rich-editor 0.7.13 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SlashCommandMenu.svelte +11 -0
- package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
- package/dist/components/TipTapEditor.svelte +5 -0
- package/dist/components/TipTapEditor.svelte.d.ts +2 -0
- package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
- package/dist/extensions/CardBlock.d.ts +20 -0
- package/dist/extensions/CardBlock.d.ts.map +1 -0
- package/dist/extensions/CardBlock.js +236 -0
- package/dist/extensions/CardBlock.test.d.ts +2 -0
- package/dist/extensions/CardBlock.test.d.ts.map +1 -0
- package/dist/extensions/CardBlock.test.js +90 -0
- package/dist/extensions/MbusVideo.d.ts.map +1 -1
- package/dist/extensions/MbusVideo.js +16 -64
- package/dist/extensions/PdfBlock.d.ts.map +1 -1
- package/dist/extensions/PdfBlock.js +15 -63
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles/editor.css +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/utils/resize.d.ts +41 -0
- package/dist/utils/resize.d.ts.map +1 -0
- package/dist/utils/resize.js +119 -0
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +42 -1
- package/package.json +1 -1
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
Columns3,
|
|
21
21
|
Type,
|
|
22
22
|
Tv,
|
|
23
|
+
SquareDashed,
|
|
23
24
|
} from "lucide-svelte";
|
|
24
25
|
import type { SlashMenuItem, ToolbarFeature, PromptHandler } from "../types";
|
|
25
26
|
import type { Component } from "svelte";
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
h2: "기본",
|
|
37
38
|
h3: "기본",
|
|
38
39
|
mbus: "미디어",
|
|
40
|
+
card: "블록",
|
|
39
41
|
"bullet-list": "리스트",
|
|
40
42
|
"ordered-list": "리스트",
|
|
41
43
|
checklist: "리스트",
|
|
@@ -184,6 +186,15 @@
|
|
|
184
186
|
if (url) editor.chain().focus().setMbusVideo({ src: url }).run();
|
|
185
187
|
},
|
|
186
188
|
},
|
|
189
|
+
{
|
|
190
|
+
feature: "card",
|
|
191
|
+
label: "카드",
|
|
192
|
+
keywords: "card 카드 상자 박스 강조 배경",
|
|
193
|
+
icon: SquareDashed,
|
|
194
|
+
command: (editor) => {
|
|
195
|
+
editor.chain().focus().setCard().run();
|
|
196
|
+
},
|
|
197
|
+
},
|
|
187
198
|
{
|
|
188
199
|
feature: "link",
|
|
189
200
|
label: "링크",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlashCommandMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/components/SlashCommandMenu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"SlashCommandMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/components/SlashCommandMenu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAuB3C,OAAO,KAAK,EAAiB,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEvC,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AA2WJ,QAAA,MAAM,gBAAgB,qCAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
import { Indent } from "../extensions/Indent";
|
|
37
37
|
import { FileAttachment } from "../extensions/FileAttachment";
|
|
38
38
|
import { MbusVideo } from "../extensions/MbusVideo";
|
|
39
|
+
import { CardBlock } from "../extensions/CardBlock";
|
|
39
40
|
import FixedToolbar from "./FixedToolbar.svelte";
|
|
40
41
|
import BubbleToolbar from "./BubbleToolbar.svelte";
|
|
41
42
|
import SlashCommandMenu from "./SlashCommandMenu.svelte";
|
|
@@ -116,6 +117,7 @@
|
|
|
116
117
|
onPromptLink,
|
|
117
118
|
onPromptImage,
|
|
118
119
|
onPromptMbus,
|
|
120
|
+
onPromptCardBackground,
|
|
119
121
|
extensions: extraExtensions = [],
|
|
120
122
|
editable = true,
|
|
121
123
|
toolbar = 'full',
|
|
@@ -130,6 +132,8 @@
|
|
|
130
132
|
onPromptLink?: PromptHandler;
|
|
131
133
|
onPromptImage?: PromptHandler;
|
|
132
134
|
onPromptMbus?: PromptHandler;
|
|
135
|
+
/** 카드 배경 고르기. 미제공 시 window.prompt 폴백 */
|
|
136
|
+
onPromptCardBackground?: PromptHandler;
|
|
133
137
|
extensions?: AnyExtension[];
|
|
134
138
|
editable?: boolean;
|
|
135
139
|
toolbar?: ToolbarMode;
|
|
@@ -380,6 +384,7 @@
|
|
|
380
384
|
...(fileDownloadBaseUrl ? { downloadBaseUrl: fileDownloadBaseUrl } : {}),
|
|
381
385
|
}),
|
|
382
386
|
MbusVideo,
|
|
387
|
+
CardBlock.configure({ promptBackground: onPromptCardBackground ?? null }),
|
|
383
388
|
Columns,
|
|
384
389
|
Column,
|
|
385
390
|
CodeBlockTopEscape,
|
|
@@ -11,6 +11,8 @@ type $$ComponentProps = {
|
|
|
11
11
|
onPromptLink?: PromptHandler;
|
|
12
12
|
onPromptImage?: PromptHandler;
|
|
13
13
|
onPromptMbus?: PromptHandler;
|
|
14
|
+
/** 카드 배경 고르기. 미제공 시 window.prompt 폴백 */
|
|
15
|
+
onPromptCardBackground?: PromptHandler;
|
|
14
16
|
extensions?: AnyExtension[];
|
|
15
17
|
editable?: boolean;
|
|
16
18
|
toolbar?: ToolbarMode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAc5D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEhE,KAAK,gBAAgB,GAAI;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,wCAAwC;IACxC,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,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;AA2lBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Node } from "@tiptap/core";
|
|
2
|
+
/** 배경 문자열을 받아 새 값(취소면 null)을 돌려주는 호스트 훅. */
|
|
3
|
+
export type CardBackgroundPrompt = (current: string) => Promise<string | null>;
|
|
4
|
+
export interface CardBlockOptions {
|
|
5
|
+
HTMLAttributes: Record<string, unknown>;
|
|
6
|
+
promptBackground: CardBackgroundPrompt | null;
|
|
7
|
+
}
|
|
8
|
+
export declare const CardBlock: Node<CardBlockOptions, any>;
|
|
9
|
+
declare module "@tiptap/core" {
|
|
10
|
+
interface Commands<ReturnType> {
|
|
11
|
+
card: {
|
|
12
|
+
setCard: (attrs?: {
|
|
13
|
+
title?: string;
|
|
14
|
+
background?: string;
|
|
15
|
+
height?: string;
|
|
16
|
+
}) => ReturnType;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=CardBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CardBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/CardBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAiCrD,4CAA4C;AAC5C,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAChC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,gBAAgB,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC9C;AAiBD,eAAO,MAAM,SAAS,6BAwNpB,CAAC;AAEH,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,IAAI,EAAE;YACL,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE;gBACjB,KAAK,CAAC,EAAE,MAAM,CAAC;gBACf,UAAU,CAAC,EAAE,MAAM,CAAC;gBACpB,MAAM,CAAC,EAAE,MAAM,CAAC;aAChB,KAAK,UAAU,CAAC;SACjB,CAAC;KACF;CACD"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { attachResize } from "../utils/resize";
|
|
3
|
+
/**
|
|
4
|
+
* 카드 블록 — 본문 중간에 넣는 **배경 있는 상자**.
|
|
5
|
+
*
|
|
6
|
+
* 제목 한 줄 + 자유 내용, 배경은 그라디언트·단색·이미지 어느 쪽이든 CSS `background` 문자열로
|
|
7
|
+
* 받는다. 원래 정올(`apps/jungol/src/components/ui/tiptap/card/`)에만 있던 것을 패키지로 올렸다.
|
|
8
|
+
*
|
|
9
|
+
* ## 이식하면서 달라진 것
|
|
10
|
+
*
|
|
11
|
+
* - **NodeView 를 vanilla JS 로 다시 썼다.** 원본은 `SvelteNodeViewRenderer` 였는데, 이 패키지의
|
|
12
|
+
* 확장은 전부 vanilla 다(`.claude/rules/rich-editor.md`). Svelte 런타임을 노드뷰에 끌어들이면
|
|
13
|
+
* 호스트 앱과 Svelte 사본이 갈릴 때 조용히 깨진다.
|
|
14
|
+
* - **배경 고르기 UI 를 패키지가 갖지 않는다.** 원본 피커는 Unsplash 검색·파일 업로드·스낵바까지
|
|
15
|
+
* 앱에 묶여 있었다. 대신 `onPromptLink` 와 같은 **핸들러 규약**으로 뺐다 —
|
|
16
|
+
* 호스트가 `editor.storage.card.promptBackground` 를 채우면 그걸 부르고, 없으면 `window.prompt`.
|
|
17
|
+
* - **높이 조절은 공용 `attachResize`** 를 쓴다. 원본은 seorii 패키지의 `MediaResize` 가
|
|
18
|
+
* `data-resize-*` 플래그를 보고 붙여 주던 것이라, 그 패키지를 떠나면 같이 사라졌다.
|
|
19
|
+
*
|
|
20
|
+
* ## 저장 형식
|
|
21
|
+
*
|
|
22
|
+
* 새로 저장하는 것은 `div[data-type="card"]` 다. 옛 정올 문서의 `<tiptap-card>` 는
|
|
23
|
+
* `parseHTML` 이 직접 받고, 서버에 남은 문자열은 `transformLegacyHtml` 이 바꾼다
|
|
24
|
+
* (file·collapsable·midibus 와 같은 경로).
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_BACKGROUND = "linear-gradient(135deg, #f8fafc 0%, #e0f2fe 55%, #fef3c7 100%)";
|
|
27
|
+
const MIN_HEIGHT = 120;
|
|
28
|
+
const MAX_HEIGHT = 640;
|
|
29
|
+
const DEFAULT_HEIGHT = 190;
|
|
30
|
+
const clampHeight = (raw) => {
|
|
31
|
+
const n = Number.parseFloat(String(raw ?? ""));
|
|
32
|
+
if (!Number.isFinite(n))
|
|
33
|
+
return DEFAULT_HEIGHT;
|
|
34
|
+
return Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, Math.round(n)));
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 배경 값은 그대로 `style.background` 에 들어간다. 사용자가 넣은 문자열이 그 선언을 닫고
|
|
38
|
+
* 다른 선언을 여는 것을 막으려면 `;` 를 지워야 한다(원본 정올 코드도 같은 방어를 한다).
|
|
39
|
+
*/
|
|
40
|
+
const safeBackground = (raw) => {
|
|
41
|
+
const value = raw.trim().replace(/;/g, "");
|
|
42
|
+
return value || DEFAULT_BACKGROUND;
|
|
43
|
+
};
|
|
44
|
+
export const CardBlock = Node.create({
|
|
45
|
+
name: "card",
|
|
46
|
+
group: "block",
|
|
47
|
+
content: "block*",
|
|
48
|
+
draggable: true,
|
|
49
|
+
addOptions() {
|
|
50
|
+
return {
|
|
51
|
+
HTMLAttributes: {},
|
|
52
|
+
promptBackground: null
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
addStorage() {
|
|
56
|
+
return {
|
|
57
|
+
promptBackground: this.options.promptBackground
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
addAttributes() {
|
|
61
|
+
return {
|
|
62
|
+
title: {
|
|
63
|
+
default: "",
|
|
64
|
+
parseHTML: (el) => el.getAttribute("data-card-title") ?? el.getAttribute("title") ?? "",
|
|
65
|
+
renderHTML: (attrs) => attrs.title ? { "data-card-title": attrs.title } : {}
|
|
66
|
+
},
|
|
67
|
+
background: {
|
|
68
|
+
default: "",
|
|
69
|
+
parseHTML: (el) => el.getAttribute("data-card-background") ?? el.getAttribute("background") ?? "",
|
|
70
|
+
renderHTML: (attrs) => attrs.background
|
|
71
|
+
? { "data-card-background": attrs.background }
|
|
72
|
+
: {}
|
|
73
|
+
},
|
|
74
|
+
height: {
|
|
75
|
+
default: String(DEFAULT_HEIGHT),
|
|
76
|
+
parseHTML: (el) => el.getAttribute("data-card-height") ?? el.getAttribute("height") ?? String(DEFAULT_HEIGHT),
|
|
77
|
+
renderHTML: (attrs) => ({ "data-card-height": String(attrs.height ?? DEFAULT_HEIGHT) })
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
parseHTML() {
|
|
82
|
+
return [
|
|
83
|
+
{ tag: 'div[data-type="card"]' },
|
|
84
|
+
// 옛 정올 스키마. `transformLegacyHtml` 이 못 잡고 지나간 것도 여기서 받는다.
|
|
85
|
+
{ tag: "tiptap-card" }
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
renderHTML({ HTMLAttributes }) {
|
|
89
|
+
return [
|
|
90
|
+
"div",
|
|
91
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
92
|
+
"data-type": "card"
|
|
93
|
+
}),
|
|
94
|
+
0
|
|
95
|
+
];
|
|
96
|
+
},
|
|
97
|
+
addNodeView() {
|
|
98
|
+
return ({ node, editor, getPos }) => {
|
|
99
|
+
let currentNode = node;
|
|
100
|
+
let detachResize = null;
|
|
101
|
+
const dom = document.createElement("div");
|
|
102
|
+
dom.setAttribute("data-type", "card");
|
|
103
|
+
dom.setAttribute("data-node-view-wrapper", "");
|
|
104
|
+
dom.className = "hce-card";
|
|
105
|
+
dom.style.position = "relative";
|
|
106
|
+
const frame = document.createElement("section");
|
|
107
|
+
frame.className = "hce-card-frame";
|
|
108
|
+
frame.setAttribute("data-card-frame", "");
|
|
109
|
+
dom.appendChild(frame);
|
|
110
|
+
const content = document.createElement("div");
|
|
111
|
+
content.className = "hce-card-content";
|
|
112
|
+
frame.appendChild(content);
|
|
113
|
+
const header = document.createElement("header");
|
|
114
|
+
header.className = "hce-card-header";
|
|
115
|
+
content.appendChild(header);
|
|
116
|
+
const body = document.createElement("div");
|
|
117
|
+
body.className = "hce-card-body";
|
|
118
|
+
content.appendChild(body);
|
|
119
|
+
/** 편집 모드에서만 만드는 제목 입력칸. 읽기 모드에서는 `<h2>`. */
|
|
120
|
+
let titleInput = null;
|
|
121
|
+
let titleHeading = null;
|
|
122
|
+
const applyAttrs = (n) => {
|
|
123
|
+
frame.style.height = `${clampHeight(n.attrs.height)}px`;
|
|
124
|
+
frame.style.background = safeBackground(String(n.attrs.background ?? ""));
|
|
125
|
+
const title = String(n.attrs.title ?? "");
|
|
126
|
+
if (titleInput) {
|
|
127
|
+
if (titleInput.value !== title)
|
|
128
|
+
titleInput.value = title;
|
|
129
|
+
}
|
|
130
|
+
else if (titleHeading) {
|
|
131
|
+
titleHeading.textContent = title;
|
|
132
|
+
titleHeading.hidden = !title;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const setAttr = (patch) => {
|
|
136
|
+
const pos = getPos();
|
|
137
|
+
if (pos == null)
|
|
138
|
+
return;
|
|
139
|
+
editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
140
|
+
...currentNode.attrs,
|
|
141
|
+
...patch
|
|
142
|
+
}));
|
|
143
|
+
};
|
|
144
|
+
if (editor.isEditable) {
|
|
145
|
+
titleInput = document.createElement("input");
|
|
146
|
+
titleInput.type = "text";
|
|
147
|
+
titleInput.className = "hce-card-title-input";
|
|
148
|
+
titleInput.placeholder = "카드 제목";
|
|
149
|
+
titleInput.contentEditable = "false";
|
|
150
|
+
// ProseMirror 가 입력칸의 키/포인터를 가져가지 않도록 막는다.
|
|
151
|
+
titleInput.setAttribute("data-card-control", "");
|
|
152
|
+
titleInput.addEventListener("input", () => {
|
|
153
|
+
setAttr({ title: titleInput?.value ?? "" });
|
|
154
|
+
});
|
|
155
|
+
header.appendChild(titleInput);
|
|
156
|
+
const pick = document.createElement("button");
|
|
157
|
+
pick.type = "button";
|
|
158
|
+
pick.className = "hce-card-bg-button";
|
|
159
|
+
pick.setAttribute("data-card-control", "");
|
|
160
|
+
pick.contentEditable = "false";
|
|
161
|
+
pick.textContent = "배경 변경";
|
|
162
|
+
pick.addEventListener("click", async (e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
// 이 패키지는 `Storage` 타입 증강을 하지 않아 인덱스 접근이 막힌다
|
|
166
|
+
// (`editor.storage.fileAttachment` 도 같은 이유로 이미 에러가 나 있다).
|
|
167
|
+
// 여기서만 unknown 을 거쳐 내려간다 — 새 타입 에러를 만들지 않기 위해서다.
|
|
168
|
+
const storage = editor.storage;
|
|
169
|
+
const prompt = storage.card
|
|
170
|
+
?.promptBackground;
|
|
171
|
+
const current = String(currentNode.attrs.background ?? "");
|
|
172
|
+
const next = prompt
|
|
173
|
+
? await prompt(current)
|
|
174
|
+
: window.prompt("카드 배경 (CSS background 값)", current);
|
|
175
|
+
if (next == null)
|
|
176
|
+
return;
|
|
177
|
+
setAttr({ background: next });
|
|
178
|
+
});
|
|
179
|
+
frame.appendChild(pick);
|
|
180
|
+
detachResize = attachResize({
|
|
181
|
+
dom: frame,
|
|
182
|
+
editor,
|
|
183
|
+
getPos,
|
|
184
|
+
getNode: () => currentNode,
|
|
185
|
+
axis: "y",
|
|
186
|
+
attr: "height",
|
|
187
|
+
min: MIN_HEIGHT,
|
|
188
|
+
max: MAX_HEIGHT,
|
|
189
|
+
label: "카드 높이 조절",
|
|
190
|
+
// 원본과 같은 저장 형식 — 단위 없는 숫자 문자열.
|
|
191
|
+
format: (v) => String(Math.round(v))
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
titleHeading = document.createElement("h2");
|
|
196
|
+
titleHeading.className = "hce-card-title";
|
|
197
|
+
header.appendChild(titleHeading);
|
|
198
|
+
}
|
|
199
|
+
applyAttrs(node);
|
|
200
|
+
return {
|
|
201
|
+
dom,
|
|
202
|
+
contentDOM: body,
|
|
203
|
+
update: (updated) => {
|
|
204
|
+
if (updated.type.name !== "card")
|
|
205
|
+
return false;
|
|
206
|
+
currentNode = updated;
|
|
207
|
+
applyAttrs(updated);
|
|
208
|
+
return true;
|
|
209
|
+
},
|
|
210
|
+
// 제목 입력칸·배경 버튼 위의 이벤트는 ProseMirror 가 가로채면 안 된다.
|
|
211
|
+
stopEvent: (event) => {
|
|
212
|
+
const target = event.target;
|
|
213
|
+
return target instanceof Element && !!target.closest("[data-card-control]");
|
|
214
|
+
},
|
|
215
|
+
destroy: () => {
|
|
216
|
+
detachResize?.();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
addCommands() {
|
|
222
|
+
return {
|
|
223
|
+
setCard: (attrs) => ({ chain }) => chain()
|
|
224
|
+
.insertContent({
|
|
225
|
+
type: this.name,
|
|
226
|
+
attrs: {
|
|
227
|
+
title: attrs?.title ?? "",
|
|
228
|
+
background: attrs?.background ?? "",
|
|
229
|
+
height: attrs?.height ?? String(DEFAULT_HEIGHT)
|
|
230
|
+
},
|
|
231
|
+
content: [{ type: "paragraph" }]
|
|
232
|
+
})
|
|
233
|
+
.run()
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CardBlock.test.d.ts","sourceRoot":"","sources":["../../src/extensions/CardBlock.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 { CardBlock } from "./CardBlock";
|
|
5
|
+
import { transformLegacyHtml } from "../utils/sanitize";
|
|
6
|
+
let editor = null;
|
|
7
|
+
const make = (content) => {
|
|
8
|
+
editor = new Editor({
|
|
9
|
+
element: document.createElement("div"),
|
|
10
|
+
extensions: [StarterKit, CardBlock],
|
|
11
|
+
content
|
|
12
|
+
});
|
|
13
|
+
return editor;
|
|
14
|
+
};
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
editor?.destroy();
|
|
17
|
+
editor = null;
|
|
18
|
+
});
|
|
19
|
+
describe("CardBlock", () => {
|
|
20
|
+
it("새 스키마(div[data-type=card])를 카드로 읽는다", () => {
|
|
21
|
+
const e = make('<div data-type="card" data-card-title="제목" data-card-background="red" data-card-height="240"><p>본문</p></div>');
|
|
22
|
+
const node = e.getJSON().content?.[0];
|
|
23
|
+
expect(node?.type).toBe("card");
|
|
24
|
+
expect(node?.attrs?.title).toBe("제목");
|
|
25
|
+
expect(node?.attrs?.background).toBe("red");
|
|
26
|
+
expect(node?.attrs?.height).toBe("240");
|
|
27
|
+
expect(node?.content?.[0]?.type).toBe("paragraph");
|
|
28
|
+
});
|
|
29
|
+
it("옛 정올 스키마(<tiptap-card>)도 그대로 읽는다", () => {
|
|
30
|
+
const e = make('<tiptap-card title="옛제목" background="blue" height="300"><p>본문</p></tiptap-card>');
|
|
31
|
+
const node = e.getJSON().content?.[0];
|
|
32
|
+
expect(node?.type).toBe("card");
|
|
33
|
+
expect(node?.attrs?.title).toBe("옛제목");
|
|
34
|
+
expect(node?.attrs?.background).toBe("blue");
|
|
35
|
+
expect(node?.attrs?.height).toBe("300");
|
|
36
|
+
});
|
|
37
|
+
it("내용을 가진 컨테이너다 — 자식 블록이 보존된다", () => {
|
|
38
|
+
const e = make('<div data-type="card"><p>첫째</p><p>둘째</p></div>');
|
|
39
|
+
const node = e.getJSON().content?.[0];
|
|
40
|
+
expect(node?.content).toHaveLength(2);
|
|
41
|
+
});
|
|
42
|
+
it("새 스키마로 다시 쓴다", () => {
|
|
43
|
+
const e = make('<tiptap-card title="T" height="200"><p>x</p></tiptap-card>');
|
|
44
|
+
const html = e.getHTML();
|
|
45
|
+
expect(html).toContain('data-type="card"');
|
|
46
|
+
expect(html).toContain('data-card-title="T"');
|
|
47
|
+
expect(html).toContain('data-card-height="200"');
|
|
48
|
+
// 옛 태그로는 저장하지 않는다
|
|
49
|
+
expect(html).not.toContain("<tiptap-card");
|
|
50
|
+
});
|
|
51
|
+
it("height 기본값은 190", () => {
|
|
52
|
+
const e = make('<div data-type="card"><p>x</p></div>');
|
|
53
|
+
expect(e.getJSON().content?.[0]?.attrs?.height).toBe("190");
|
|
54
|
+
});
|
|
55
|
+
it("setCard 명령이 빈 문단 하나를 가진 카드를 넣는다", () => {
|
|
56
|
+
const e = make("<p></p>");
|
|
57
|
+
e.commands.setCard();
|
|
58
|
+
const card = e.getJSON().content?.find((n) => n.type === "card");
|
|
59
|
+
expect(card).toBeTruthy();
|
|
60
|
+
expect(card?.content?.[0]?.type).toBe("paragraph");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("transformLegacyHtml — tiptap-card", () => {
|
|
64
|
+
it("속성 순서와 무관하게 바꾼다", () => {
|
|
65
|
+
const a = transformLegacyHtml('<tiptap-card title="T" background="B" height="200"><p>x</p></tiptap-card>');
|
|
66
|
+
const b = transformLegacyHtml('<tiptap-card height="200" background="B" title="T"><p>x</p></tiptap-card>');
|
|
67
|
+
for (const out of [a, b]) {
|
|
68
|
+
expect(out).toContain('data-type="card"');
|
|
69
|
+
expect(out).toContain('data-card-title="T"');
|
|
70
|
+
expect(out).toContain('data-card-background="B"');
|
|
71
|
+
expect(out).toContain('data-card-height="200"');
|
|
72
|
+
expect(out).toContain("<p>x</p>");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
it("속성이 없어도 기본 높이를 붙인다", () => {
|
|
76
|
+
const out = transformLegacyHtml("<tiptap-card><p>x</p></tiptap-card>");
|
|
77
|
+
expect(out).toContain('data-card-height="190"');
|
|
78
|
+
expect(out).not.toContain("data-card-title");
|
|
79
|
+
});
|
|
80
|
+
it("중첩 블록이 있는 본문을 통째로 보존한다", () => {
|
|
81
|
+
const out = transformLegacyHtml('<tiptap-card title="T"><p>하나</p><ul><li>둘</li></ul></tiptap-card>');
|
|
82
|
+
expect(out).toContain("<ul><li>둘</li></ul>");
|
|
83
|
+
});
|
|
84
|
+
it("카드가 둘 이상이어도 각각 바꾼다", () => {
|
|
85
|
+
const out = transformLegacyHtml('<tiptap-card title="A"><p>1</p></tiptap-card><tiptap-card title="B"><p>2</p></tiptap-card>');
|
|
86
|
+
expect(out.match(/data-type="card"/g)).toHaveLength(2);
|
|
87
|
+
expect(out).toContain('data-card-title="A"');
|
|
88
|
+
expect(out).toContain('data-card-title="B"');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MbusVideo.d.ts","sourceRoot":"","sources":["../../src/extensions/MbusVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"MbusVideo.d.ts","sourceRoot":"","sources":["../../src/extensions/MbusVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAGrD,MAAM,WAAW,gBAAgB;IAChC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,SAAS,EAAE;YACV,YAAY,EAAE,CAAC,KAAK,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAC;SACrE,CAAC;KACF;CACD;AAED,eAAO,MAAM,SAAS,6BAwHpB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { attachResize } from "../utils/resize";
|
|
2
3
|
export const MbusVideo = Node.create({
|
|
3
4
|
name: "mbusVideo",
|
|
4
5
|
group: "block",
|
|
@@ -39,6 +40,9 @@ export const MbusVideo = Node.create({
|
|
|
39
40
|
},
|
|
40
41
|
addNodeView() {
|
|
41
42
|
return ({ node, editor, getPos }) => {
|
|
43
|
+
// 리사이즈가 attrs 를 되쓰므로 **항상 최신 노드**여야 한다(복붙 시절의 stale 버그).
|
|
44
|
+
let currentNode = node;
|
|
45
|
+
let detachResize = null;
|
|
42
46
|
const dom = document.createElement("div");
|
|
43
47
|
dom.setAttribute("data-type", "mbusVideo");
|
|
44
48
|
dom.setAttribute("data-node-view-wrapper", "");
|
|
@@ -72,80 +76,28 @@ export const MbusVideo = Node.create({
|
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
dom.appendChild(del);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
bar.style.cssText =
|
|
83
|
-
"display:block;width:3px;height:48px;background:var(--border, #d1d5db);border-radius:2px;transition:background 0.15s;pointer-events:none;";
|
|
84
|
-
resizeHandle.appendChild(bar);
|
|
85
|
-
resizeHandle.addEventListener("mouseenter", () => {
|
|
86
|
-
bar.style.background = "var(--primary, #3382f2)";
|
|
79
|
+
detachResize = attachResize({
|
|
80
|
+
dom,
|
|
81
|
+
editor,
|
|
82
|
+
getPos: () => (typeof getPos === "function" ? getPos() : undefined),
|
|
83
|
+
getNode: () => currentNode,
|
|
84
|
+
axis: "x",
|
|
85
|
+
label: "영상 너비 조절"
|
|
87
86
|
});
|
|
88
|
-
resizeHandle.addEventListener("mouseleave", () => {
|
|
89
|
-
if (!resizing)
|
|
90
|
-
bar.style.background = "var(--border, #d1d5db)";
|
|
91
|
-
});
|
|
92
|
-
const MIN_W = 240;
|
|
93
|
-
const MAX_W = 1600;
|
|
94
|
-
let resizing = false;
|
|
95
|
-
let startX = 0;
|
|
96
|
-
let startWidth = 0;
|
|
97
|
-
resizeHandle.addEventListener("pointerdown", (e) => {
|
|
98
|
-
e.preventDefault();
|
|
99
|
-
e.stopPropagation();
|
|
100
|
-
resizing = true;
|
|
101
|
-
startX = e.clientX;
|
|
102
|
-
startWidth = dom.getBoundingClientRect().width;
|
|
103
|
-
bar.style.background = "var(--primary, #3382f2)";
|
|
104
|
-
resizeHandle.setPointerCapture(e.pointerId);
|
|
105
|
-
});
|
|
106
|
-
resizeHandle.addEventListener("pointermove", (e) => {
|
|
107
|
-
if (!resizing)
|
|
108
|
-
return;
|
|
109
|
-
const delta = e.clientX - startX;
|
|
110
|
-
const next = Math.max(MIN_W, Math.min(MAX_W, startWidth + delta));
|
|
111
|
-
dom.style.width = `${next}px`;
|
|
112
|
-
});
|
|
113
|
-
const endResize = (e) => {
|
|
114
|
-
if (!resizing)
|
|
115
|
-
return;
|
|
116
|
-
resizing = false;
|
|
117
|
-
bar.style.background = "var(--border, #d1d5db)";
|
|
118
|
-
try {
|
|
119
|
-
resizeHandle.releasePointerCapture(e.pointerId);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
// ignore
|
|
123
|
-
}
|
|
124
|
-
const finalWidth = dom.style.width;
|
|
125
|
-
if (!finalWidth)
|
|
126
|
-
return;
|
|
127
|
-
const pos = typeof getPos === "function" ? getPos() : null;
|
|
128
|
-
if (pos == null)
|
|
129
|
-
return;
|
|
130
|
-
editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
131
|
-
...node.attrs,
|
|
132
|
-
width: finalWidth
|
|
133
|
-
}));
|
|
134
|
-
};
|
|
135
|
-
resizeHandle.addEventListener("pointerup", endResize);
|
|
136
|
-
resizeHandle.addEventListener("pointercancel", endResize);
|
|
137
|
-
dom.appendChild(resizeHandle);
|
|
138
87
|
}
|
|
139
88
|
return {
|
|
140
89
|
dom,
|
|
141
90
|
update: (updated) => {
|
|
142
|
-
if (updated.type !==
|
|
91
|
+
if (updated.type !== currentNode.type)
|
|
143
92
|
return false;
|
|
144
93
|
const w = updated.attrs.width;
|
|
145
94
|
dom.style.width = w || "";
|
|
95
|
+
currentNode = updated;
|
|
146
96
|
return true;
|
|
147
97
|
},
|
|
148
|
-
destroy: () => {
|
|
98
|
+
destroy: () => {
|
|
99
|
+
detachResize?.();
|
|
100
|
+
}
|
|
149
101
|
};
|
|
150
102
|
};
|
|
151
103
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdfBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/PdfBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,UAAU,EAAmB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"PdfBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/PdfBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,UAAU,EAAmB,MAAM,cAAc,CAAC;AAKnE,eAAO,MAAM,QAAQ,sBAyYnB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Node as TiptapNode, mergeAttributes } from "@tiptap/core";
|
|
2
2
|
import { getPdfJs } from "../utils/pdf";
|
|
3
|
+
import { attachResize } from "../utils/resize";
|
|
3
4
|
export const PdfBlock = TiptapNode.create({
|
|
4
5
|
name: "pdfBlock",
|
|
5
6
|
group: "block",
|
|
@@ -79,6 +80,10 @@ export const PdfBlock = TiptapNode.create({
|
|
|
79
80
|
let destroyed = false;
|
|
80
81
|
let resizeObserver = null;
|
|
81
82
|
let resizeTimeout;
|
|
83
|
+
// 리사이즈가 `setNodeMarkup` 으로 attrs 를 되쓰기 때문에 **항상 최신 노드**여야 한다.
|
|
84
|
+
// 클로저의 `node` 를 쓰면 그 사이의 다른 속성 변경을 리사이즈가 되돌린다.
|
|
85
|
+
let currentNode = node;
|
|
86
|
+
let detachResize = null;
|
|
82
87
|
const dom = document.createElement("div");
|
|
83
88
|
dom.classList.add("my-4");
|
|
84
89
|
dom.contentEditable = "false";
|
|
@@ -97,69 +102,14 @@ export const PdfBlock = TiptapNode.create({
|
|
|
97
102
|
dom.appendChild(wrapper);
|
|
98
103
|
// 리사이즈 핸들 (편집 가능 모드에서만)
|
|
99
104
|
if (editor.isEditable) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
bar.style.cssText =
|
|
108
|
-
"display:block;width:3px;height:48px;background:var(--border, #d1d5db);border-radius:2px;transition:background 0.15s;pointer-events:none;";
|
|
109
|
-
resizeHandle.appendChild(bar);
|
|
110
|
-
resizeHandle.addEventListener("mouseenter", () => {
|
|
111
|
-
bar.style.background = "var(--primary, #3382f2)";
|
|
105
|
+
detachResize = attachResize({
|
|
106
|
+
dom,
|
|
107
|
+
editor,
|
|
108
|
+
getPos,
|
|
109
|
+
getNode: () => currentNode,
|
|
110
|
+
axis: "x",
|
|
111
|
+
label: "PDF 너비 조절",
|
|
112
112
|
});
|
|
113
|
-
resizeHandle.addEventListener("mouseleave", () => {
|
|
114
|
-
if (!resizing)
|
|
115
|
-
bar.style.background = "var(--border, #d1d5db)";
|
|
116
|
-
});
|
|
117
|
-
const MIN_W = 240;
|
|
118
|
-
const MAX_W = 1600;
|
|
119
|
-
let resizing = false;
|
|
120
|
-
let startX = 0;
|
|
121
|
-
let startWidth = 0;
|
|
122
|
-
resizeHandle.addEventListener("pointerdown", (e) => {
|
|
123
|
-
e.preventDefault();
|
|
124
|
-
e.stopPropagation();
|
|
125
|
-
resizing = true;
|
|
126
|
-
startX = e.clientX;
|
|
127
|
-
startWidth = dom.getBoundingClientRect().width;
|
|
128
|
-
bar.style.background = "var(--primary, #3382f2)";
|
|
129
|
-
resizeHandle.setPointerCapture(e.pointerId);
|
|
130
|
-
});
|
|
131
|
-
resizeHandle.addEventListener("pointermove", (e) => {
|
|
132
|
-
if (!resizing)
|
|
133
|
-
return;
|
|
134
|
-
const delta = e.clientX - startX;
|
|
135
|
-
const next = Math.max(MIN_W, Math.min(MAX_W, startWidth + delta));
|
|
136
|
-
dom.style.width = `${next}px`;
|
|
137
|
-
});
|
|
138
|
-
const endResize = (e) => {
|
|
139
|
-
if (!resizing)
|
|
140
|
-
return;
|
|
141
|
-
resizing = false;
|
|
142
|
-
bar.style.background = "var(--border, #d1d5db)";
|
|
143
|
-
try {
|
|
144
|
-
resizeHandle.releasePointerCapture(e.pointerId);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
// ignore release errors (handle may have lost capture already)
|
|
148
|
-
}
|
|
149
|
-
const finalWidth = dom.style.width;
|
|
150
|
-
if (!finalWidth)
|
|
151
|
-
return;
|
|
152
|
-
const pos = getPos();
|
|
153
|
-
if (pos == null)
|
|
154
|
-
return;
|
|
155
|
-
editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
156
|
-
...node.attrs,
|
|
157
|
-
width: finalWidth,
|
|
158
|
-
}));
|
|
159
|
-
};
|
|
160
|
-
resizeHandle.addEventListener("pointerup", endResize);
|
|
161
|
-
resizeHandle.addEventListener("pointercancel", endResize);
|
|
162
|
-
dom.appendChild(resizeHandle);
|
|
163
113
|
}
|
|
164
114
|
// Header
|
|
165
115
|
const header = document.createElement("div");
|
|
@@ -405,9 +355,10 @@ export const PdfBlock = TiptapNode.create({
|
|
|
405
355
|
if (updatedNode.type.name !== "pdfBlock")
|
|
406
356
|
return false;
|
|
407
357
|
const newWidth = updatedNode.attrs.width;
|
|
408
|
-
if (newWidth !==
|
|
358
|
+
if (newWidth !== currentNode.attrs.width) {
|
|
409
359
|
dom.style.width = newWidth || "";
|
|
410
360
|
}
|
|
361
|
+
currentNode = updatedNode;
|
|
411
362
|
return true;
|
|
412
363
|
},
|
|
413
364
|
selectNode: () => { },
|
|
@@ -416,6 +367,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
416
367
|
destroyed = true;
|
|
417
368
|
clearTimeout(resizeTimeout);
|
|
418
369
|
resizeObserver?.disconnect();
|
|
370
|
+
detachResize?.();
|
|
419
371
|
},
|
|
420
372
|
};
|
|
421
373
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -11,11 +11,15 @@ export { FixedDetails } from "./extensions/FixedDetails";
|
|
|
11
11
|
export { FileAttachment } from "./extensions/FileAttachment";
|
|
12
12
|
export type { FileResolver, FileResolveResult } from "./extensions/FileAttachment";
|
|
13
13
|
export { MbusVideo } from "./extensions/MbusVideo";
|
|
14
|
+
export { CardBlock } from "./extensions/CardBlock";
|
|
15
|
+
export type { CardBackgroundPrompt, CardBlockOptions } from "./extensions/CardBlock";
|
|
14
16
|
export { Columns } from "./extensions/Columns";
|
|
15
17
|
export { Column } from "./extensions/Column";
|
|
16
18
|
export { MathInline, MathDisplay } from "./extensions/Math";
|
|
17
19
|
export { sanitizeHtml, stripHtmlToExcerpt, transformLegacyHtml } from "./utils/sanitize";
|
|
18
20
|
export { configurePdfJs, getPdfJs } from "./utils/pdf";
|
|
21
|
+
export { attachResize } from "./utils/resize";
|
|
22
|
+
export type { AttachResizeOptions, ResizeAxis } from "./utils/resize";
|
|
19
23
|
export { cn } from "./utils/cn";
|
|
20
24
|
export type { TipTapEditorProps, UploadHandler, ToolbarMode, ToolbarFeature, FixedToolbarProps, BlockHandleProps, SlashCommandMenuProps, PdfViewerProps, SlashMenuItem, } from "./types";
|
|
21
25
|
export { TOOLBAR_PRESETS, resolveFeatures } from "./types";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAGhC,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,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;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,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG5D,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;AAGhC,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -12,11 +12,13 @@ export { Indent } from "./extensions/Indent";
|
|
|
12
12
|
export { FixedDetails } from "./extensions/FixedDetails";
|
|
13
13
|
export { FileAttachment } from "./extensions/FileAttachment";
|
|
14
14
|
export { MbusVideo } from "./extensions/MbusVideo";
|
|
15
|
+
export { CardBlock } from "./extensions/CardBlock";
|
|
15
16
|
export { Columns } from "./extensions/Columns";
|
|
16
17
|
export { Column } from "./extensions/Column";
|
|
17
18
|
export { MathInline, MathDisplay } from "./extensions/Math";
|
|
18
19
|
// 유틸리티
|
|
19
20
|
export { sanitizeHtml, stripHtmlToExcerpt, transformLegacyHtml } from "./utils/sanitize";
|
|
20
21
|
export { configurePdfJs, getPdfJs } from "./utils/pdf";
|
|
22
|
+
export { attachResize } from "./utils/resize";
|
|
21
23
|
export { cn } from "./utils/cn";
|
|
22
24
|
export { TOOLBAR_PRESETS, resolveFeatures } from "./types";
|
package/dist/styles/editor.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.top-full{top:100%}.z-30{z-index:30}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-px{height:1px}.w-10{width:2.5rem}.w-4{width:1rem}.w-\[360px\]{width:360px}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-px{width:1px}.min-w-\[96px\]{min-width:96px}.max-w-\[90vw\]{max-width:90vw}.flex-shrink{flex-shrink:1}.-translate-x-1\/2{--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.columns-2{-moz-columns:2;column-count:2}.columns-3{-moz-columns:3;column-count:3}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:var(--radius-md,4px)}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-xl,8px)}.rounded-md{border-radius:var(--radius-md,6px)}.rounded-xl{border-radius:var(--radius-2xl,12px)}.rounded-t-xl{border-top-left-radius:var(--radius-2xl,12px);border-top-right-radius:var(--radius-2xl,12px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-border{border-color:var(--border)}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-transparent{background-color:transparent}.bg-white\/20{background-color:hsla(0,0%,100%,.2)}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-bottom:.5rem}.pt-2,.py-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.opacity-30{opacity:.3}.shadow-lg{--tw-shadow:var(--shadow-dropdown,0 10px 15px -3px rgba(0,0,0,.1));--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:var(--shadow-dropdown,0 1px 2px 0 rgba(0,0,0,.05));--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:var(--shadow-popup,0 20px 25px -5px rgba(0,0,0,.1));--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hce-editor-wrapper{overflow:visible!important}.hce-color-divider{border-top:1px solid hsla(0,0%,100%,.15)}.bubble-toolbar-container button,.hce-editor-wrapper button,.slash-menu button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;cursor:pointer;box-sizing:border-box}.bubble-toolbar-container button:disabled,.hce-editor-wrapper button:disabled,.slash-menu button:disabled{cursor:default}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap p{margin:.5em 0!important;line-height:1.5}.tiptap p:first-child{margin-top:0!important}.tiptap p:last-child{margin-bottom:0!important}.tiptap p:empty,.tiptap p:has(>br:only-child){min-height:1.5em}.tiptap h1,.tiptap h2,.tiptap h3,.tiptap li,.tiptap p{white-space:pre-wrap}.tiptap h1{font-size:2rem;font-weight:700;margin:1.5em 0 .5em;line-height:1.25}.tiptap h2{font-size:1.5rem;font-weight:600;margin:1.2em 0 .4em;line-height:1.3}.tiptap h3{font-size:1.25rem;font-weight:600;margin:1em 0 .3em;line-height:1.35}.tiptap ul{list-style:disc}.tiptap ol,.tiptap ul{padding-left:1.5em;margin:.5em 0}.tiptap ol{list-style:decimal}.tiptap ul ul{list-style:circle}.tiptap ul ul ul{list-style:square}.tiptap ol ol{list-style:lower-alpha}.tiptap ol ol ol{list-style:lower-roman}.tiptap li{margin:.4em 0;line-height:1.7}.tiptap li>p{margin:0!important;line-height:inherit!important}.tiptap ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap ul[data-type=taskList] li{display:flex;align-items:baseline;gap:.5rem;margin:.3em 0;line-height:1.7;list-style:none;list-style-type:none}.tiptap ul[data-type=taskList] li>label{flex-shrink:0;display:flex;align-items:center;height:1.5em}.tiptap ul[data-type=taskList] li>label input[type=checkbox]{-moz-appearance:none;appearance:none;-webkit-appearance:none;width:1.1em;height:1.1em;margin:0;padding:0;cursor:pointer;background-color:var(--background,#fff);border:1.5px solid var(--border);border-radius:4px;transition:background-color .12s ease,border-color .12s ease,box-shadow .12s ease;display:inline-block;vertical-align:middle;flex-shrink:0}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:hover{border-color:var(--muted-foreground)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:focus-visible{outline:none;box-shadow:0 0 0 2px var(--background,#fff),0 0 0 4px var(--primary)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:checked{background-color:var(--primary);border-color:var(--primary);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' viewBox='0 0 16 16'%3E%3Cpath d='m3.5 8.5 3 3 6-6.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.tiptap ul[data-type=taskList] li>div{flex:1}.tiptap ul[data-type=taskList] li[data-checked=true]>div{text-decoration:line-through;opacity:.6}.tiptap blockquote{border-left:3px solid var(--border);padding:.5rem 1rem;margin:.75em 0;color:var(--muted-foreground);font-style:italic}.tiptap hr{border:none;border-top:2px solid color-mix(in srgb,var(--foreground) 38%,transparent);margin:1.5em 0}.tiptap code{background:var(--muted);border-radius:4px;padding:.15em .35em;font-size:.9em}.tiptap pre{background:#282c34;color:#abb2bf;border-radius:8px;padding:1em;margin:.75em 0;overflow-x:auto;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:1em;line-height:1.6}.tiptap pre code{background:none;padding:0;border-radius:0;color:#abb2bf;font-size:inherit}.tiptap pre code .hljs-keyword,.tiptap pre code .hljs-operator{color:#c678dd}.tiptap pre code .hljs-addition,.tiptap pre code .hljs-doctag,.tiptap pre code .hljs-regexp,.tiptap pre code .hljs-string{color:#98c379}.tiptap pre code .hljs-attr,.tiptap pre code .hljs-number,.tiptap pre code .hljs-selector-class,.tiptap pre code .hljs-template-variable,.tiptap pre code .hljs-type,.tiptap pre code .hljs-variable{color:#d19a66}.tiptap pre code .hljs-function,.tiptap pre code .hljs-section,.tiptap pre code .hljs-title,.tiptap pre code .hljs-title.class_{color:#61aeee}.tiptap pre code .hljs-params,.tiptap pre code .hljs-punctuation{color:#abb2bf}.tiptap pre code .hljs-comment,.tiptap pre code .hljs-quote{color:#5c6370;font-style:italic}.tiptap pre code .hljs-built_in,.tiptap pre code .hljs-formula{color:#e6c07b}.tiptap pre code .hljs-meta,.tiptap pre code .hljs-meta .hljs-keyword{color:#61aeee}.tiptap pre code .hljs-meta .hljs-string{color:#98c379}.tiptap pre code .hljs-attribute,.tiptap pre code .hljs-deletion,.tiptap pre code .hljs-name,.tiptap pre code .hljs-property,.tiptap pre code .hljs-selector-tag,.tiptap pre code .hljs-subst,.tiptap pre code .hljs-tag{color:#e06c75}.tiptap pre code .hljs-literal{color:#56b6c2}.tiptap pre code .hljs-selector-attr,.tiptap pre code .hljs-selector-id,.tiptap pre code .hljs-selector-pseudo{color:#61aeee}.tiptap pre code .hljs-link{color:#61aeee;text-decoration:underline}.tiptap pre code .hljs-bullet,.tiptap pre code .hljs-symbol{color:#61aeee}.tiptap pre code .hljs-emphasis{font-style:italic}.tiptap pre code .hljs-strong{font-weight:700}.tiptap img{max-width:100%;height:auto;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.tiptap [data-type=pdfBlock].ProseMirror-selectednode>div,.tiptap img{border-radius:8px}.tiptap a{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.tiptap a:hover{opacity:.8}.tiptap mark{background-color:#fef08a;border-radius:2px;padding:.05em .1em}.tiptap [data-type=details]{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;display:grid;grid-template-columns:26px 1fr;align-items:start;transition:background .12s}.tiptap [data-type=details]:hover{background:var(--muted)}.tiptap [data-type=details]>button{background:none;border:none;cursor:pointer;padding:0;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s;flex-shrink:0;margin-top:13px;margin-left:4px}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:5px solid;border-top:4px solid transparent;border-bottom:4px solid transparent;margin-left:2px}.tiptap [data-type=details]:hover>button{color:var(--foreground)}.tiptap [data-type=details].is-open>button{transform:rotate(90deg)}.tiptap [data-type=details]>div{flex:1;min-width:0}.tiptap [data-type=details] summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 0;display:block;border-bottom:1px solid transparent;transition:border-color .15s}.tiptap [data-type=details].is-open>div>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap [data-type=detailsContent]{padding:8px 8px 8px 0;border-left:none}.tiptap [data-type=detailsContent][hidden]{display:none}.tiptap details{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;transition:background .12s}.tiptap details:hover{background:var(--muted)}.tiptap details summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 26px;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-bottom:1px solid transparent;position:relative;list-style:none;transition:border-color .15s}.tiptap details[open]>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap details summary::-webkit-details-marker{display:none}.tiptap details summary::marker{content:""}.tiptap details summary:before{content:"";position:absolute;left:8px;top:50%;width:0;height:0;border-left:5px solid var(--muted-foreground);border-top:4px solid transparent;border-bottom:4px solid transparent;transform:translateY(-50%);transition:transform .2s,border-left-color .15s}.tiptap details:hover summary:before{border-left-color:var(--foreground)}.tiptap details[open]>summary:before{transform:translateY(-50%) rotate(90deg)}.tiptap details[open]>:not(summary){margin-top:8px;padding-left:26px;padding-right:8px}.tiptap table{border-collapse:collapse;margin:1em 0;table-layout:fixed;border:2px solid var(--border);max-width:100%}.tiptap .tableWrapper{overflow-x:auto;max-width:100%}.tiptap td,.tiptap th{border:1px solid var(--border);padding:.6em .8em;min-width:60px;vertical-align:middle;position:relative;box-sizing:border-box;overflow:hidden;word-break:break-word;line-height:1.5}.tiptap th{background:var(--muted);font-weight:600;text-align:left}.tiptap td{background:var(--background)}.tiptap .selectedCell:after{content:"";position:absolute;inset:0;background:var(--primary);opacity:.12;pointer-events:none;z-index:1}.tiptap .column-resize-handle{position:absolute;right:-3px;top:0;bottom:0;width:6px;background:var(--primary);opacity:0;cursor:col-resize;z-index:20;border-radius:2px;transition:opacity .15s}.tiptap table:hover .column-resize-handle{opacity:.4}.tiptap .column-resize-handle:hover{opacity:1}.tiptap.resize-cursor{cursor:col-resize}.tableWrapper{overflow-x:auto;overflow-y:hidden;margin:1em 0;max-width:100%;padding-right:4px}.tiptap table .grip-column,.tiptap table .grip-row,.tiptap table .grip-table{display:none!important}.tiptap .is-empty:before{content:attr(data-placeholder);float:left;color:var(--muted-foreground);opacity:.5;pointer-events:none;height:0}.tiptap h1.is-empty:before{font-size:2rem;font-weight:700}.tiptap h2.is-empty:before{font-size:1.5rem;font-weight:600}.tiptap h3.is-empty:before{font-size:1.25rem;font-weight:600}.ProseMirror-gapcursor{display:none;pointer-events:none;position:relative}.ProseMirror-gapcursor:after{content:"";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid var(--foreground);animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.ProseMirror-dropcursor{border-top:2px solid var(--primary);margin:-1px 0}.tiptap ::-moz-selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.tiptap ::selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.ProseMirror-noderangeselection ::-moz-selection{background:transparent}.ProseMirror-noderangeselection ::selection{background:transparent}.ProseMirror-noderangeselection *{caret-color:transparent}.ProseMirror-selectednode,.ProseMirror-selectednoderange{position:relative}.ProseMirror-selectednode:not(img):not([data-type=pdfBlock]):before,.ProseMirror-selectednoderange:not([data-type=pdfBlock]):before{content:"";position:absolute;inset:-.25rem;background-color:color-mix(in srgb,var(--primary) 10%,transparent);border:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:.5rem;pointer-events:none;z-index:-1}.bubble-toolbar-container{z-index:50}.hce-floating-plus{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--background);color:var(--muted-foreground);cursor:pointer;transition:all .15s}.hce-floating-plus:hover{color:var(--foreground);border-color:var(--foreground);background:var(--muted)}.hce-drag-handle{display:flex;align-items:center;gap:2px}.hce-drag-handle__plus{display:flex;align-items:center;justify-content:center;padding:4px;border-radius:4px;border:none;background:transparent;color:var(--muted-foreground);opacity:.5;cursor:pointer;transition:opacity .15s,background-color .15s}.hce-drag-handle__plus:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip{display:flex;align-items:center;justify-content:center;padding:2px;border-radius:4px;color:var(--muted-foreground);opacity:.5;cursor:grab;transition:opacity .15s,background-color .15s}.hce-drag-handle__grip:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip:active{cursor:grabbing}.tiptap div[data-type=columns]{display:grid;gap:16px;margin:1em 0}.tiptap div[data-type=columns][data-columns="2"]{grid-template-columns:1fr 1fr}.tiptap div[data-type=columns][data-columns="3"]{grid-template-columns:1fr 1fr 1fr}.tiptap div[data-type=column]{min-width:0;padding:4px 6px;border:1px dashed var(--border);border-radius:8px}.tiptap[contenteditable=true] div[data-type=column]:focus-within{border-color:var(--primary);border-style:solid}.tiptap div[data-type=columns].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:8px}@media (max-width:640px){.tiptap div[data-type=columns]{grid-template-columns:1fr!important}}.hce-editor-wrapper button[data-tooltip]{position:relative}.hce-editor-wrapper button[data-tooltip]:hover:after{content:attr(data-tooltip);position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;z-index:60}.hce-table-btn-wrap{position:relative;display:inline-flex}.hce-table-btn-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .15s;z-index:60}.hce-table-btn-wrap:hover .hce-table-btn-tooltip{opacity:1}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{border-color:var(--primary)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:opacity-30:disabled{opacity:.3}
|
|
1
|
+
.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.top-full{top:100%}.z-30{z-index:30}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-px{height:1px}.w-10{width:2.5rem}.w-4{width:1rem}.w-\[360px\]{width:360px}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-px{width:1px}.min-w-\[96px\]{min-width:96px}.max-w-\[90vw\]{max-width:90vw}.flex-shrink{flex-shrink:1}.-translate-x-1\/2{--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.columns-2{-moz-columns:2;column-count:2}.columns-3{-moz-columns:3;column-count:3}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:var(--radius-md,4px)}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-xl,8px)}.rounded-md{border-radius:var(--radius-md,6px)}.rounded-xl{border-radius:var(--radius-2xl,12px)}.rounded-t-xl{border-top-left-radius:var(--radius-2xl,12px);border-top-right-radius:var(--radius-2xl,12px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-border{border-color:var(--border)}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.bg-background{background-color:var(--background)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-border{background-color:var(--border)}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-transparent{background-color:transparent}.bg-white\/20{background-color:hsla(0,0%,100%,.2)}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-bottom:.5rem}.pt-2,.py-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.opacity-30{opacity:.3}.shadow-lg{--tw-shadow:var(--shadow-dropdown,0 10px 15px -3px rgba(0,0,0,.1));--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:var(--shadow-dropdown,0 1px 2px 0 rgba(0,0,0,.05));--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:var(--shadow-popup,0 20px 25px -5px rgba(0,0,0,.1));--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hce-editor-wrapper{overflow:visible!important}.hce-color-divider{border-top:1px solid hsla(0,0%,100%,.15)}.bubble-toolbar-container button,.hce-editor-wrapper button,.slash-menu button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;cursor:pointer;box-sizing:border-box}.bubble-toolbar-container button:disabled,.hce-editor-wrapper button:disabled,.slash-menu button:disabled{cursor:default}.tiptap{font-size:1.0625rem;line-height:1.8;color:var(--foreground);padding:1.5rem}.tiptap>*+*{margin-top:.25em}.tiptap p{margin:.5em 0!important;line-height:1.5}.tiptap p:first-child{margin-top:0!important}.tiptap p:last-child{margin-bottom:0!important}.tiptap p:empty,.tiptap p:has(>br:only-child){min-height:1.5em}.tiptap h1,.tiptap h2,.tiptap h3,.tiptap li,.tiptap p{white-space:pre-wrap}.tiptap h1{font-size:2rem;font-weight:700;margin:1.5em 0 .5em;line-height:1.25}.tiptap h2{font-size:1.5rem;font-weight:600;margin:1.2em 0 .4em;line-height:1.3}.tiptap h3{font-size:1.25rem;font-weight:600;margin:1em 0 .3em;line-height:1.35}.tiptap ul{list-style:disc}.tiptap ol,.tiptap ul{padding-left:1.5em;margin:.5em 0}.tiptap ol{list-style:decimal}.tiptap ul ul{list-style:circle}.tiptap ul ul ul{list-style:square}.tiptap ol ol{list-style:lower-alpha}.tiptap ol ol ol{list-style:lower-roman}.tiptap li{margin:.4em 0;line-height:1.7}.tiptap li>p{margin:0!important;line-height:inherit!important}.tiptap ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap ul[data-type=taskList] li{display:flex;align-items:baseline;gap:.5rem;margin:.3em 0;line-height:1.7;list-style:none;list-style-type:none}.tiptap ul[data-type=taskList] li>label{flex-shrink:0;display:flex;align-items:center;height:1.5em}.tiptap ul[data-type=taskList] li>label input[type=checkbox]{-moz-appearance:none;appearance:none;-webkit-appearance:none;width:1.1em;height:1.1em;margin:0;padding:0;cursor:pointer;background-color:var(--background,#fff);border:1.5px solid var(--border);border-radius:4px;transition:background-color .12s ease,border-color .12s ease,box-shadow .12s ease;display:inline-block;vertical-align:middle;flex-shrink:0}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:hover{border-color:var(--muted-foreground)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:focus-visible{outline:none;box-shadow:0 0 0 2px var(--background,#fff),0 0 0 4px var(--primary)}.tiptap ul[data-type=taskList] li>label input[type=checkbox]:checked{background-color:var(--primary);border-color:var(--primary);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' viewBox='0 0 16 16'%3E%3Cpath d='m3.5 8.5 3 3 6-6.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.tiptap ul[data-type=taskList] li>div{flex:1}.tiptap ul[data-type=taskList] li[data-checked=true]>div{text-decoration:line-through;opacity:.6}.tiptap blockquote{border-left:3px solid var(--border);padding:.5rem 1rem;margin:.75em 0;color:var(--muted-foreground);font-style:italic}.tiptap hr{border:none;border-top:2px solid color-mix(in srgb,var(--foreground) 38%,transparent);margin:1.5em 0}.tiptap code{background:var(--muted);border-radius:4px;padding:.15em .35em;font-size:.9em}.tiptap pre{background:#282c34;color:#abb2bf;border-radius:8px;padding:1em;margin:.75em 0;overflow-x:auto;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:1em;line-height:1.6}.tiptap pre code{background:none;padding:0;border-radius:0;color:#abb2bf;font-size:inherit}.tiptap pre code .hljs-keyword,.tiptap pre code .hljs-operator{color:#c678dd}.tiptap pre code .hljs-addition,.tiptap pre code .hljs-doctag,.tiptap pre code .hljs-regexp,.tiptap pre code .hljs-string{color:#98c379}.tiptap pre code .hljs-attr,.tiptap pre code .hljs-number,.tiptap pre code .hljs-selector-class,.tiptap pre code .hljs-template-variable,.tiptap pre code .hljs-type,.tiptap pre code .hljs-variable{color:#d19a66}.tiptap pre code .hljs-function,.tiptap pre code .hljs-section,.tiptap pre code .hljs-title,.tiptap pre code .hljs-title.class_{color:#61aeee}.tiptap pre code .hljs-params,.tiptap pre code .hljs-punctuation{color:#abb2bf}.tiptap pre code .hljs-comment,.tiptap pre code .hljs-quote{color:#5c6370;font-style:italic}.tiptap pre code .hljs-built_in,.tiptap pre code .hljs-formula{color:#e6c07b}.tiptap pre code .hljs-meta,.tiptap pre code .hljs-meta .hljs-keyword{color:#61aeee}.tiptap pre code .hljs-meta .hljs-string{color:#98c379}.tiptap pre code .hljs-attribute,.tiptap pre code .hljs-deletion,.tiptap pre code .hljs-name,.tiptap pre code .hljs-property,.tiptap pre code .hljs-selector-tag,.tiptap pre code .hljs-subst,.tiptap pre code .hljs-tag{color:#e06c75}.tiptap pre code .hljs-literal{color:#56b6c2}.tiptap pre code .hljs-selector-attr,.tiptap pre code .hljs-selector-id,.tiptap pre code .hljs-selector-pseudo{color:#61aeee}.tiptap pre code .hljs-link{color:#61aeee;text-decoration:underline}.tiptap pre code .hljs-bullet,.tiptap pre code .hljs-symbol{color:#61aeee}.tiptap pre code .hljs-emphasis{font-style:italic}.tiptap pre code .hljs-strong{font-weight:700}.tiptap img{max-width:100%;height:auto;margin:.75em 0;cursor:pointer;transition:box-shadow .15s}.tiptap [data-type=pdfBlock].ProseMirror-selectednode>div,.tiptap img{border-radius:8px}.tiptap a{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.tiptap a:hover{opacity:.8}.tiptap mark{background-color:#fef08a;border-radius:2px;padding:.05em .1em}.tiptap [data-type=details]{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;display:grid;grid-template-columns:26px 1fr;align-items:start;transition:background .12s}.tiptap [data-type=details]:hover{background:var(--muted)}.tiptap [data-type=details]>button{background:none;border:none;cursor:pointer;padding:0;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground);border-radius:4px;transition:transform .2s,color .15s;flex-shrink:0;margin-top:13px;margin-left:4px}.tiptap [data-type=details]>button:after{content:"";display:block;width:0;height:0;border-left:5px solid;border-top:4px solid transparent;border-bottom:4px solid transparent;margin-left:2px}.tiptap [data-type=details]:hover>button{color:var(--foreground)}.tiptap [data-type=details].is-open>button{transform:rotate(90deg)}.tiptap [data-type=details]>div{flex:1;min-width:0}.tiptap [data-type=details] summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 0;display:block;border-bottom:1px solid transparent;transition:border-color .15s}.tiptap [data-type=details].is-open>div>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap [data-type=detailsContent]{padding:8px 8px 8px 0;border-left:none}.tiptap [data-type=detailsContent][hidden]{display:none}.tiptap details{border:none;border-radius:6px;padding:0;margin:.5em 0;background:transparent;transition:background .12s}.tiptap details:hover{background:var(--muted)}.tiptap details summary{font-weight:600;cursor:pointer;padding:10px 8px 10px 26px;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-bottom:1px solid transparent;position:relative;list-style:none;transition:border-color .15s}.tiptap details[open]>summary{border-bottom-color:var(--border,#e5e7eb)}.tiptap details summary::-webkit-details-marker{display:none}.tiptap details summary::marker{content:""}.tiptap details summary:before{content:"";position:absolute;left:8px;top:50%;width:0;height:0;border-left:5px solid var(--muted-foreground);border-top:4px solid transparent;border-bottom:4px solid transparent;transform:translateY(-50%);transition:transform .2s,border-left-color .15s}.tiptap details:hover summary:before{border-left-color:var(--foreground)}.tiptap details[open]>summary:before{transform:translateY(-50%) rotate(90deg)}.tiptap details[open]>:not(summary){margin-top:8px;padding-left:26px;padding-right:8px}.tiptap table{border-collapse:collapse;margin:1em 0;table-layout:fixed;border:2px solid var(--border);max-width:100%}.tiptap .tableWrapper{overflow-x:auto;max-width:100%}.tiptap td,.tiptap th{border:1px solid var(--border);padding:.6em .8em;min-width:60px;vertical-align:middle;position:relative;box-sizing:border-box;overflow:hidden;word-break:break-word;line-height:1.5}.tiptap th{background:var(--muted);font-weight:600;text-align:left}.tiptap td{background:var(--background)}.tiptap .selectedCell:after{content:"";position:absolute;inset:0;background:var(--primary);opacity:.12;pointer-events:none;z-index:1}.tiptap .column-resize-handle{position:absolute;right:-3px;top:0;bottom:0;width:6px;background:var(--primary);opacity:0;cursor:col-resize;z-index:20;border-radius:2px;transition:opacity .15s}.tiptap table:hover .column-resize-handle{opacity:.4}.tiptap .column-resize-handle:hover{opacity:1}.tiptap.resize-cursor{cursor:col-resize}.tableWrapper{overflow-x:auto;overflow-y:hidden;margin:1em 0;max-width:100%;padding-right:4px}.tiptap table .grip-column,.tiptap table .grip-row,.tiptap table .grip-table{display:none!important}.tiptap .is-empty:before{content:attr(data-placeholder);float:left;color:var(--muted-foreground);opacity:.5;pointer-events:none;height:0}.tiptap h1.is-empty:before{font-size:2rem;font-weight:700}.tiptap h2.is-empty:before{font-size:1.5rem;font-weight:600}.tiptap h3.is-empty:before{font-size:1.25rem;font-weight:600}.ProseMirror-gapcursor{display:none;pointer-events:none;position:relative}.ProseMirror-gapcursor:after{content:"";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid var(--foreground);animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.ProseMirror-dropcursor{border-top:2px solid var(--primary);margin:-1px 0}.tiptap ::-moz-selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.tiptap ::selection{background-color:color-mix(in srgb,var(--primary) 15%,transparent)}.ProseMirror-noderangeselection ::-moz-selection{background:transparent}.ProseMirror-noderangeselection ::selection{background:transparent}.ProseMirror-noderangeselection *{caret-color:transparent}.ProseMirror-selectednode,.ProseMirror-selectednoderange{position:relative}.ProseMirror-selectednode:not(img):not([data-type=pdfBlock]):before,.ProseMirror-selectednoderange:not([data-type=pdfBlock]):before{content:"";position:absolute;inset:-.25rem;background-color:color-mix(in srgb,var(--primary) 10%,transparent);border:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:.5rem;pointer-events:none;z-index:-1}.bubble-toolbar-container{z-index:50}.hce-floating-plus{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--background);color:var(--muted-foreground);cursor:pointer;transition:all .15s}.hce-floating-plus:hover{color:var(--foreground);border-color:var(--foreground);background:var(--muted)}.hce-drag-handle{display:flex;align-items:center;gap:2px}.hce-drag-handle__plus{display:flex;align-items:center;justify-content:center;padding:4px;border-radius:4px;border:none;background:transparent;color:var(--muted-foreground);opacity:.5;cursor:pointer;transition:opacity .15s,background-color .15s}.hce-drag-handle__plus:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip{display:flex;align-items:center;justify-content:center;padding:2px;border-radius:4px;color:var(--muted-foreground);opacity:.5;cursor:grab;transition:opacity .15s,background-color .15s}.hce-drag-handle__grip:hover{opacity:1;background:var(--muted)}.hce-drag-handle__grip:active{cursor:grabbing}.tiptap div[data-type=columns]{display:grid;gap:16px;margin:1em 0}.tiptap div[data-type=columns][data-columns="2"]{grid-template-columns:1fr 1fr}.tiptap div[data-type=columns][data-columns="3"]{grid-template-columns:1fr 1fr 1fr}.tiptap div[data-type=column]{min-width:0;padding:4px 6px;border:1px dashed var(--border);border-radius:8px}.tiptap[contenteditable=true] div[data-type=column]:focus-within{border-color:var(--primary);border-style:solid}.tiptap div[data-type=columns].ProseMirror-selectednode{outline:2px solid color-mix(in srgb,var(--primary) 30%,transparent);border-radius:8px}@media (max-width:640px){.tiptap div[data-type=columns]{grid-template-columns:1fr!important}}.hce-editor-wrapper button[data-tooltip]{position:relative}.hce-editor-wrapper button[data-tooltip]:hover:after{content:attr(data-tooltip);position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;z-index:60}.hce-table-btn-wrap{position:relative;display:inline-flex}.hce-table-btn-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:6px;padding:4px 8px;font-size:11px;line-height:1.2;white-space:nowrap;color:#fff;background:#1e293b;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .15s;z-index:60}.hce-table-btn-wrap:hover .hce-table-btn-tooltip{opacity:1}.hce-card-frame,.tiptap [data-type=card]{position:relative}.hce-card-frame{box-sizing:border-box;width:100%;margin:12px 0;padding:26px 30px;display:flex;flex-direction:column;justify-content:center;gap:12px;overflow:hidden;border:1px solid hsla(0,0%,100%,.52);border-radius:8px;box-shadow:inset 0 0 0 1px hsla(0,0%,100%,.28),0 8px 24px rgba(15,23,42,.08);overflow-wrap:anywhere;isolation:isolate}.hce-card-frame:before{content:"";position:absolute;inset:0;z-index:0;background:linear-gradient(90deg,hsla(0,0%,100%,.14),hsla(0,0%,100%,.04) 48%,rgba(15,23,42,.08));pointer-events:none}.hce-card-content{position:relative;z-index:1;box-sizing:border-box;width:100%;max-height:100%;min-height:0;padding:18px 20px;display:flex;flex-direction:column;gap:10px;overflow:hidden;border:1px solid hsla(0,0%,100%,.56);border-radius:8px;background:hsla(0,0%,100%,.82);color:#111827;box-shadow:0 14px 42px rgba(15,23,42,.12),inset 0 1px 0 hsla(0,0%,100%,.52);backdrop-filter:blur(18px) saturate(1.16);-webkit-backdrop-filter:blur(18px) saturate(1.16)}@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.hce-card-content{background:hsla(0,0%,100%,.9)}}.hce-card-header{flex:0 0 auto}.hce-card-title{margin:0;font-size:1.65rem;font-weight:700;line-height:1.18;color:#111827}.hce-card-title[hidden]{display:none}.hce-card-title-input{box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.7);color:#111827;font:inherit;font-size:1.15rem;font-weight:700;outline:none}.hce-card-title-input:focus{border-color:var(--primary,#3382f2)}.hce-card-body{flex:1 1 auto;min-height:0;overflow:auto;font-size:1rem;line-height:1.55;color:#111827}.hce-card-body>p:first-child{margin-top:0}.hce-card-body>p:last-child{margin-bottom:0}.hce-card-bg-button{position:absolute;top:8px;right:8px;z-index:3;padding:4px 10px;border:1px solid rgba(17,24,39,.12);border-radius:6px;background:hsla(0,0%,100%,.86);color:#111827;font-size:.8125rem;line-height:1.4;cursor:pointer}.hce-card-bg-button:hover{background:#fff}@media (max-width:640px){.hce-card-frame{padding:18px}.hce-card-content{padding:14px 16px}.hce-card-title{font-size:1.35rem}}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{border-color:var(--primary)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:opacity-30:disabled{opacity:.3}
|
package/dist/types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type PromptHandler = (defaultValue: string) => Promise<string | null>;
|
|
|
12
12
|
/** 툴바 모드 */
|
|
13
13
|
export type ToolbarMode = 'minimal' | 'standard' | 'full';
|
|
14
14
|
/** 개별 툴바 기능 ID */
|
|
15
|
-
export type ToolbarFeature = 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'superscript' | 'subscript' | 'code' | 'text-color' | 'align-left' | 'align-center' | 'align-right' | 'paragraph' | 'h1' | 'h2' | 'h3' | 'bullet-list' | 'ordered-list' | 'checklist' | 'blockquote' | 'horizontal-rule' | 'toggle' | 'link' | 'image' | 'pdf' | 'file' | 'mbus' | 'columns-2' | 'columns-3' | 'table' | 'code-block' | 'undo' | 'redo' | 'fixed-toolbar' | 'bubble-toolbar' | 'slash-menu' | 'table-menu' | 'character-count' | 'upload-overlay';
|
|
15
|
+
export type ToolbarFeature = 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'superscript' | 'subscript' | 'code' | 'text-color' | 'align-left' | 'align-center' | 'align-right' | 'paragraph' | 'h1' | 'h2' | 'h3' | 'bullet-list' | 'ordered-list' | 'checklist' | 'blockquote' | 'horizontal-rule' | 'toggle' | 'link' | 'image' | 'pdf' | 'file' | 'mbus' | 'card' | 'columns-2' | 'columns-3' | 'table' | 'code-block' | 'undo' | 'redo' | 'fixed-toolbar' | 'bubble-toolbar' | 'slash-menu' | 'table-menu' | 'character-count' | 'upload-overlay';
|
|
16
16
|
/** 모드별 기본 feature 프리셋 */
|
|
17
17
|
export declare const TOOLBAR_PRESETS: Record<ToolbarMode, ToolbarFeature[]>;
|
|
18
18
|
/** features 배열 → Set 변환. features가 있으면 그걸 쓰고 없으면 toolbar 모드 프리셋 */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,+BAA+B;AAC/B,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7E,YAAY;AACZ,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,kBAAkB;AAClB,MAAM,MAAM,cAAc,GAEvB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GACxD,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAEnD,YAAY,GAAG,cAAc,GAAG,aAAa,GAE7C,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAEhC,aAAa,GAAG,cAAc,GAAG,WAAW,GAC5C,YAAY,GAAG,iBAAiB,GAAG,QAAQ,GAE3C,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,+BAA+B;AAC/B,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7E,YAAY;AACZ,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,kBAAkB;AAClB,MAAM,MAAM,cAAc,GAEvB,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GACxD,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,YAAY,GAEnD,YAAY,GAAG,cAAc,GAAG,aAAa,GAE7C,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAEhC,aAAa,GAAG,cAAc,GAAG,WAAW,GAC5C,YAAY,GAAG,iBAAiB,GAAG,QAAQ,GAE3C,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAEnD,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,GAElD,MAAM,GAAG,MAAM,GAEf,eAAe,GAAG,gBAAgB,GAAG,YAAY,GACjD,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAEvD,yBAAyB;AACzB,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,EAAE,CAgCjE,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,eAAe,CAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,cAAc,EAAE,GACzB,GAAG,CAAC,cAAc,CAAC,CAErB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,uCAAuC;IACvC,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,yFAAyF;IACzF,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,0DAA0D;IAC1D,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC"}
|
package/dist/types.js
CHANGED
|
@@ -7,7 +7,7 @@ export const TOOLBAR_PRESETS = {
|
|
|
7
7
|
'paragraph', 'h1', 'h2', 'h3',
|
|
8
8
|
'bullet-list', 'ordered-list', 'checklist',
|
|
9
9
|
'blockquote', 'horizontal-rule', 'toggle',
|
|
10
|
-
'link', 'image', 'pdf', 'file', 'mbus',
|
|
10
|
+
'link', 'image', 'pdf', 'file', 'mbus', 'card',
|
|
11
11
|
'columns-2', 'columns-3', 'table', 'code-block',
|
|
12
12
|
'undo', 'redo',
|
|
13
13
|
'fixed-toolbar', 'slash-menu',
|
|
@@ -19,7 +19,7 @@ export const TOOLBAR_PRESETS = {
|
|
|
19
19
|
'paragraph', 'h1', 'h2', 'h3',
|
|
20
20
|
'bullet-list', 'ordered-list', 'checklist',
|
|
21
21
|
'blockquote', 'horizontal-rule', 'toggle',
|
|
22
|
-
'link', 'image', 'pdf', 'file', 'mbus',
|
|
22
|
+
'link', 'image', 'pdf', 'file', 'mbus', 'card',
|
|
23
23
|
'columns-2', 'columns-3', 'table', 'code-block',
|
|
24
24
|
'fixed-toolbar', 'slash-menu', 'table-menu'
|
|
25
25
|
],
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
|
3
|
+
/**
|
|
4
|
+
* 노드뷰에 드래그 리사이즈 손잡이를 단다.
|
|
5
|
+
*
|
|
6
|
+
* 원래 `PdfBlock` 과 `MbusVideo` 가 **같은 코드를 복붙**해서 각자 들고 있었다(변수명·매직넘버·
|
|
7
|
+
* cssText 문자열까지 동일). 세 번째 소비자(카드)가 생기면서 한 곳으로 모은다.
|
|
8
|
+
*
|
|
9
|
+
* 합치면서 세 가지가 달라졌다:
|
|
10
|
+
*
|
|
11
|
+
* 1. **세로 축이 생겼다.** 기존 둘은 가로(폭)만 있었다. 카드·접기처럼 내용이 흐르는 블록은
|
|
12
|
+
* 폭보다 높이를 잡고 싶다.
|
|
13
|
+
* 2. **`node` 를 getter 로 받는다.** 기존 코드는 `addNodeView` 클로저가 잡은 `node` 를 그대로
|
|
14
|
+
* `setNodeMarkup(pos, undefined, {...node.attrs, width})` 에 썼다. 그 사이 다른 속성이
|
|
15
|
+
* 바뀌었으면 **리사이즈가 그 변경을 되돌려 버린다.** 항상 최신 노드를 읽도록 바꿨다.
|
|
16
|
+
* 3. **정리 함수를 돌려준다.** 기존엔 `destroy()` 에서 손잡이 리스너를 떼지 않았다.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ 손잡이는 `position:absolute` 로 노드뷰 밖 12px 을 침범한다. 부모가 `overflow:hidden` 이면
|
|
19
|
+
* 잘려서 안 보인다(`editor.css` 가 `padding-right:4px` 로 땜빵 중인 이유).
|
|
20
|
+
*/
|
|
21
|
+
export type ResizeAxis = "x" | "y";
|
|
22
|
+
export interface AttachResizeOptions {
|
|
23
|
+
/** 크기를 실제로 먹일 요소. 보통 노드뷰 루트(`dom`). */
|
|
24
|
+
dom: HTMLElement;
|
|
25
|
+
editor: Editor;
|
|
26
|
+
getPos: () => number | undefined;
|
|
27
|
+
/** ⚠️ 값이 아니라 **getter**. 클로저가 낡은 노드를 잡는 것을 막는다. */
|
|
28
|
+
getNode: () => ProseMirrorNode;
|
|
29
|
+
/** 기본 `'x'`(폭). `'y'` 면 높이를 조절한다. */
|
|
30
|
+
axis?: ResizeAxis;
|
|
31
|
+
/** 크기를 저장할 노드 속성 이름. 기본은 축에 따라 `width` / `height`. */
|
|
32
|
+
attr?: string;
|
|
33
|
+
min?: number;
|
|
34
|
+
max?: number;
|
|
35
|
+
/** 스크린리더용 이름. 예: `'PDF 너비 조절'` */
|
|
36
|
+
label?: string;
|
|
37
|
+
/** 저장 형식. 기본은 `'240px'` 같은 px 문자열. */
|
|
38
|
+
format?: (value: number) => string;
|
|
39
|
+
}
|
|
40
|
+
export declare function attachResize(options: AttachResizeOptions): () => void;
|
|
41
|
+
//# sourceMappingURL=resize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/utils/resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjC,mDAAmD;IACnD,OAAO,EAAE,MAAM,eAAe,CAAC;IAC/B,qCAAqC;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAUD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,IAAI,CAiIrE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
x: { min: 240, max: 1600, label: "너비 조절", cursor: "ew-resize" },
|
|
3
|
+
y: { min: 120, max: 900, label: "높이 조절", cursor: "ns-resize" }
|
|
4
|
+
};
|
|
5
|
+
const IDLE = "var(--border, #d1d5db)";
|
|
6
|
+
const ACTIVE = "var(--primary, #3382f2)";
|
|
7
|
+
export function attachResize(options) {
|
|
8
|
+
const { dom, editor, getPos, getNode, axis = "x", attr = axis === "x" ? "width" : "height", format = (v) => `${Math.round(v)}px` } = options;
|
|
9
|
+
const preset = DEFAULTS[axis];
|
|
10
|
+
const min = options.min ?? preset.min;
|
|
11
|
+
const max = options.max ?? preset.max;
|
|
12
|
+
const handle = document.createElement("button");
|
|
13
|
+
handle.type = "button";
|
|
14
|
+
handle.contentEditable = "false";
|
|
15
|
+
handle.className = "hce-resize-handle";
|
|
16
|
+
handle.setAttribute("aria-label", options.label ?? preset.label);
|
|
17
|
+
handle.dataset.resizeAxis = axis;
|
|
18
|
+
handle.style.cssText =
|
|
19
|
+
axis === "x"
|
|
20
|
+
? "position:absolute;right:-12px;top:0;bottom:0;width:14px;padding:0;margin:0;border:0;background:transparent;cursor:ew-resize;z-index:2;display:flex;align-items:center;justify-content:center;"
|
|
21
|
+
: "position:absolute;left:0;right:0;bottom:-12px;height:14px;padding:0;margin:0;border:0;background:transparent;cursor:ns-resize;z-index:2;display:flex;align-items:center;justify-content:center;";
|
|
22
|
+
const bar = document.createElement("span");
|
|
23
|
+
bar.style.cssText =
|
|
24
|
+
axis === "x"
|
|
25
|
+
? `display:block;width:3px;height:48px;background:${IDLE};border-radius:2px;transition:background 0.15s;pointer-events:none;`
|
|
26
|
+
: `display:block;width:48px;height:3px;background:${IDLE};border-radius:2px;transition:background 0.15s;pointer-events:none;`;
|
|
27
|
+
handle.appendChild(bar);
|
|
28
|
+
let resizing = false;
|
|
29
|
+
let start = 0;
|
|
30
|
+
let startSize = 0;
|
|
31
|
+
const paint = (active) => {
|
|
32
|
+
bar.style.background = active ? ACTIVE : IDLE;
|
|
33
|
+
};
|
|
34
|
+
const onEnter = () => paint(true);
|
|
35
|
+
const onLeave = () => {
|
|
36
|
+
if (!resizing)
|
|
37
|
+
paint(false);
|
|
38
|
+
};
|
|
39
|
+
/*
|
|
40
|
+
* ⚠️ **`setPointerCapture` 에 기대지 않는다.**
|
|
41
|
+
*
|
|
42
|
+
* 손잡이는 14px 밖에 안 되는데 드래그는 그 밖으로 한참 벗어난다. 캡처가 잡히면 그래도
|
|
43
|
+
* 이벤트가 오지만, 캡처는 조용히 실패할 수 있다(합성 포인터·다른 요소가 이미 캡처를
|
|
44
|
+
* 쥔 경우 등). 그러면 `pointermove`/`pointerup` 이 손잡이에 안 오고, 드래그가 커밋되지
|
|
45
|
+
* 않은 채 원래 크기로 되돌아간다 — 에러 없이.
|
|
46
|
+
*
|
|
47
|
+
* 그래서 `pointerdown` 에서 **window 에 붙였다 떼는** 방식으로 간다. 캡처 실패에도,
|
|
48
|
+
* 포인터가 창 밖으로 나가도 견딘다.
|
|
49
|
+
*/
|
|
50
|
+
const onPointerMove = (e) => {
|
|
51
|
+
if (!resizing)
|
|
52
|
+
return;
|
|
53
|
+
const delta = (axis === "x" ? e.clientX : e.clientY) - start;
|
|
54
|
+
const next = Math.max(min, Math.min(max, startSize + delta));
|
|
55
|
+
if (axis === "x")
|
|
56
|
+
dom.style.width = `${next}px`;
|
|
57
|
+
else
|
|
58
|
+
dom.style.height = `${next}px`;
|
|
59
|
+
};
|
|
60
|
+
const endResize = () => {
|
|
61
|
+
if (!resizing)
|
|
62
|
+
return;
|
|
63
|
+
resizing = false;
|
|
64
|
+
paint(false);
|
|
65
|
+
detachWindow();
|
|
66
|
+
document.body.style.removeProperty("user-select");
|
|
67
|
+
document.body.style.removeProperty("cursor");
|
|
68
|
+
const raw = axis === "x" ? dom.style.width : dom.style.height;
|
|
69
|
+
if (!raw)
|
|
70
|
+
return;
|
|
71
|
+
const pos = getPos();
|
|
72
|
+
if (pos == null)
|
|
73
|
+
return;
|
|
74
|
+
const parsed = Number.parseFloat(raw);
|
|
75
|
+
if (!Number.isFinite(parsed))
|
|
76
|
+
return;
|
|
77
|
+
// ⚠️ `getNode()` — 클로저가 잡아 둔 낡은 노드를 쓰면 리사이즈가 그 사이의 다른
|
|
78
|
+
// 속성 변경을 되돌린다(복붙 시절의 버그).
|
|
79
|
+
const node = getNode();
|
|
80
|
+
editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
81
|
+
...node.attrs,
|
|
82
|
+
[attr]: format(parsed)
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
const attachWindow = () => {
|
|
86
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
87
|
+
window.addEventListener("pointerup", endResize);
|
|
88
|
+
window.addEventListener("pointercancel", endResize);
|
|
89
|
+
};
|
|
90
|
+
const detachWindow = () => {
|
|
91
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
92
|
+
window.removeEventListener("pointerup", endResize);
|
|
93
|
+
window.removeEventListener("pointercancel", endResize);
|
|
94
|
+
};
|
|
95
|
+
const onPointerDown = (e) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
resizing = true;
|
|
99
|
+
const rect = dom.getBoundingClientRect();
|
|
100
|
+
start = axis === "x" ? e.clientX : e.clientY;
|
|
101
|
+
startSize = axis === "x" ? rect.width : rect.height;
|
|
102
|
+
paint(true);
|
|
103
|
+
// 드래그 중 텍스트가 선택되며 파랗게 물드는 것을 막는다.
|
|
104
|
+
document.body.style.userSelect = "none";
|
|
105
|
+
document.body.style.cursor = preset.cursor;
|
|
106
|
+
attachWindow();
|
|
107
|
+
};
|
|
108
|
+
handle.addEventListener("mouseenter", onEnter);
|
|
109
|
+
handle.addEventListener("mouseleave", onLeave);
|
|
110
|
+
handle.addEventListener("pointerdown", onPointerDown);
|
|
111
|
+
dom.appendChild(handle);
|
|
112
|
+
return () => {
|
|
113
|
+
handle.removeEventListener("mouseenter", onEnter);
|
|
114
|
+
handle.removeEventListener("mouseleave", onLeave);
|
|
115
|
+
handle.removeEventListener("pointerdown", onPointerDown);
|
|
116
|
+
detachWindow();
|
|
117
|
+
handle.remove();
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAgDA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcjD;AA0BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA8ExD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAWrE"}
|
package/dist/utils/sanitize.js
CHANGED
|
@@ -15,7 +15,27 @@ const ALLOWED_ATTRS = {
|
|
|
15
15
|
col: new Set(["style", "width"]),
|
|
16
16
|
td: new Set(["colspan", "rowspan", "colwidth", "style"]),
|
|
17
17
|
th: new Set(["colspan", "rowspan", "colwidth", "style"]),
|
|
18
|
-
div
|
|
18
|
+
// ⚠️ 에디터가 실제로 뱉는 `div` 속성을 전부 적는다. 여기 빠진 것은 조용히 지워져
|
|
19
|
+
// 블록이 평범한 빈 `div` 로 내려앉는다 — 파일·영상·단·카드가 그렇게 사라진다.
|
|
20
|
+
// (`data-pdf-*` 만 있던 시절엔 나머지가 전부 누락돼 있었다. `sanitizeHtml` 이
|
|
21
|
+
// 아직 아무 데서도 호출되지 않아 드러나지 않았을 뿐이다.)
|
|
22
|
+
div: new Set([
|
|
23
|
+
"data-type",
|
|
24
|
+
"data-pdf-src",
|
|
25
|
+
"data-pdf-name",
|
|
26
|
+
"data-pdf-id",
|
|
27
|
+
"data-pdf-width",
|
|
28
|
+
"data-file-id",
|
|
29
|
+
"data-file-src",
|
|
30
|
+
"data-file-name",
|
|
31
|
+
"data-file-size",
|
|
32
|
+
"data-mbus-src",
|
|
33
|
+
"data-mbus-width",
|
|
34
|
+
"data-card-title",
|
|
35
|
+
"data-card-background",
|
|
36
|
+
"data-card-height",
|
|
37
|
+
"style",
|
|
38
|
+
]),
|
|
19
39
|
pre: new Set(["class"]),
|
|
20
40
|
code: new Set(["class"]),
|
|
21
41
|
"tiptap-midibus": new Set(["id", "start", "uuid", "width", "height"]),
|
|
@@ -70,6 +90,27 @@ export function transformLegacyHtml(html) {
|
|
|
70
90
|
// <tiptap-collapsable title="X">content</tiptap-collapsable>
|
|
71
91
|
// → <details><summary>X</summary><div>content</div></details>
|
|
72
92
|
.replace(/<tiptap-collapsable\s+title="([^"]*)">([\s\S]*?)<\/tiptap-collapsable>/gi, '<details><summary>$1</summary><div>$2</div></details>')
|
|
93
|
+
// <tiptap-card title="T" background="B" height="H">content</tiptap-card>
|
|
94
|
+
// → <div data-type="card" data-card-*>content</div>
|
|
95
|
+
//
|
|
96
|
+
// 정올 전용이던 카드 블록. 속성이 셋 다 선택적이고 순서도 보장되지 않아,
|
|
97
|
+
// 여는 태그를 통째로 잡은 뒤 그 안에서 개별 속성을 뽑는다 —
|
|
98
|
+
// 위 embed 처럼 순서별 규칙을 여러 개 두면 3! 가지가 되어 감당이 안 된다.
|
|
99
|
+
.replace(/<tiptap-card\b([^>]*)>([\s\S]*?)<\/tiptap-card>/gi, (_match, rawAttrs, inner) => {
|
|
100
|
+
const pick = (name) => {
|
|
101
|
+
const m = rawAttrs.match(new RegExp(`\\b${name}="([^"]*)"`, "i"));
|
|
102
|
+
return m ? m[1] : "";
|
|
103
|
+
};
|
|
104
|
+
const attrs = [
|
|
105
|
+
`data-type="card"`,
|
|
106
|
+
pick("title") && `data-card-title="${pick("title")}"`,
|
|
107
|
+
pick("background") && `data-card-background="${pick("background")}"`,
|
|
108
|
+
`data-card-height="${pick("height") || "190"}"`,
|
|
109
|
+
]
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.join(" ");
|
|
112
|
+
return `<div ${attrs}>${inner}</div>`;
|
|
113
|
+
})
|
|
73
114
|
// <embed src="X" type="application/pdf" ...>
|
|
74
115
|
// → <div data-pdf-src="X" data-pdf-name="filename">
|
|
75
116
|
.replace(/<embed\s+[^>]*src="([^"]*)"[^>]*type="application\/pdf"[^>]*\/?>/gi, (_match, src) => {
|