@teriusu/rich-editor 0.4.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/components/BubbleToolbar.svelte +26 -5
- package/dist/components/BubbleToolbar.svelte.d.ts +2 -1
- package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -1
- package/dist/components/FixedToolbar.svelte +76 -3
- package/dist/components/FixedToolbar.svelte.d.ts +2 -0
- package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
- package/dist/components/SlashCommandMenu.svelte +25 -2
- package/dist/components/SlashCommandMenu.svelte.d.ts +2 -0
- package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
- package/dist/components/TipTapEditor.svelte +41 -23
- package/dist/components/TipTapEditor.svelte.d.ts +2 -1
- package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
- package/dist/extensions/FileAttachment.d.ts.map +1 -1
- package/dist/extensions/FileAttachment.js +44 -4
- package/dist/extensions/PdfBlock.d.ts.map +1 -1
- package/dist/extensions/PdfBlock.js +19 -14
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles/editor.css +1 -698
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -1
- package/package.json +5 -2
|
@@ -21,18 +21,20 @@
|
|
|
21
21
|
Columns2,
|
|
22
22
|
Columns3,
|
|
23
23
|
} from "lucide-svelte";
|
|
24
|
-
import type { SlashMenuItem } from "../types";
|
|
24
|
+
import type { SlashMenuItem, ToolbarFeature } from "../types";
|
|
25
25
|
import type { Component } from "svelte";
|
|
26
26
|
|
|
27
27
|
const SI = 14;
|
|
28
28
|
|
|
29
29
|
const SLASH_MENU_ITEMS_DATA: {
|
|
30
|
+
feature: ToolbarFeature;
|
|
30
31
|
label: string;
|
|
31
32
|
keywords: string;
|
|
32
33
|
icon: Component<{ size?: number }>;
|
|
33
34
|
command: (editor: Editor) => void;
|
|
34
35
|
}[] = [
|
|
35
36
|
{
|
|
37
|
+
feature: "h1",
|
|
36
38
|
label: "제목 1",
|
|
37
39
|
keywords: "heading h1 제목",
|
|
38
40
|
icon: Heading1,
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
|
41
43
|
},
|
|
42
44
|
{
|
|
45
|
+
feature: "h2",
|
|
43
46
|
label: "제목 2",
|
|
44
47
|
keywords: "heading h2 제목",
|
|
45
48
|
icon: Heading2,
|
|
@@ -47,6 +50,7 @@
|
|
|
47
50
|
editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
48
51
|
},
|
|
49
52
|
{
|
|
53
|
+
feature: "h3",
|
|
50
54
|
label: "제목 3",
|
|
51
55
|
keywords: "heading h3 제목",
|
|
52
56
|
icon: Heading3,
|
|
@@ -54,36 +58,42 @@
|
|
|
54
58
|
editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
55
59
|
},
|
|
56
60
|
{
|
|
61
|
+
feature: "bullet-list",
|
|
57
62
|
label: "글머리 목록",
|
|
58
63
|
keywords: "bullet list 목록 리스트",
|
|
59
64
|
icon: List,
|
|
60
65
|
command: (editor) => editor.chain().focus().toggleBulletList().run(),
|
|
61
66
|
},
|
|
62
67
|
{
|
|
68
|
+
feature: "ordered-list",
|
|
63
69
|
label: "번호 목록",
|
|
64
70
|
keywords: "ordered number list 번호 리스트",
|
|
65
71
|
icon: ListOrdered,
|
|
66
72
|
command: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
|
67
73
|
},
|
|
68
74
|
{
|
|
75
|
+
feature: "checklist",
|
|
69
76
|
label: "체크리스트",
|
|
70
77
|
keywords: "checklist task todo 체크 할일",
|
|
71
78
|
icon: CheckSquare,
|
|
72
79
|
command: (editor) => editor.chain().focus().toggleTaskList().run(),
|
|
73
80
|
},
|
|
74
81
|
{
|
|
82
|
+
feature: "blockquote",
|
|
75
83
|
label: "인용문",
|
|
76
84
|
keywords: "quote blockquote 인용",
|
|
77
85
|
icon: Quote,
|
|
78
86
|
command: (editor) => editor.chain().focus().toggleBlockquote().run(),
|
|
79
87
|
},
|
|
80
88
|
{
|
|
89
|
+
feature: "horizontal-rule",
|
|
81
90
|
label: "구분선",
|
|
82
91
|
keywords: "divider hr horizontal rule 구분",
|
|
83
92
|
icon: Minus,
|
|
84
93
|
command: (editor) => editor.chain().focus().setHorizontalRule().run(),
|
|
85
94
|
},
|
|
86
95
|
{
|
|
96
|
+
feature: "code-block",
|
|
87
97
|
label: "C++ 코드",
|
|
88
98
|
keywords: "code cpp c++ 코드 블록",
|
|
89
99
|
icon: Code2,
|
|
@@ -91,6 +101,7 @@
|
|
|
91
101
|
editor.chain().focus().setCodeBlock({ language: "cpp" }).run(),
|
|
92
102
|
},
|
|
93
103
|
{
|
|
104
|
+
feature: "code-block",
|
|
94
105
|
label: "Python 코드",
|
|
95
106
|
keywords: "code python 파이썬 코드 블록",
|
|
96
107
|
icon: Code2,
|
|
@@ -98,12 +109,14 @@
|
|
|
98
109
|
editor.chain().focus().setCodeBlock({ language: "python" }).run(),
|
|
99
110
|
},
|
|
100
111
|
{
|
|
112
|
+
feature: "toggle",
|
|
101
113
|
label: "토글",
|
|
102
114
|
keywords: "toggle details 접기 펼치기 토글",
|
|
103
115
|
icon: ChevronRight,
|
|
104
116
|
command: (editor) => editor.chain().focus().setDetails().run(),
|
|
105
117
|
},
|
|
106
118
|
{
|
|
119
|
+
feature: "table",
|
|
107
120
|
label: "표",
|
|
108
121
|
keywords: "table 표 테이블",
|
|
109
122
|
icon: TableIcon,
|
|
@@ -115,18 +128,21 @@
|
|
|
115
128
|
.run(),
|
|
116
129
|
},
|
|
117
130
|
{
|
|
131
|
+
feature: "columns-2",
|
|
118
132
|
label: "2단 컬럼",
|
|
119
133
|
keywords: "column 컬럼 2단 분할",
|
|
120
134
|
icon: Columns2,
|
|
121
135
|
command: (editor) => editor.chain().focus().setColumns(2).run(),
|
|
122
136
|
},
|
|
123
137
|
{
|
|
138
|
+
feature: "columns-3",
|
|
124
139
|
label: "3단 컬럼",
|
|
125
140
|
keywords: "column 컬럼 3단 분할",
|
|
126
141
|
icon: Columns3,
|
|
127
142
|
command: (editor) => editor.chain().focus().setColumns(3).run(),
|
|
128
143
|
},
|
|
129
144
|
{
|
|
145
|
+
feature: "youtube",
|
|
130
146
|
label: "YouTube 영상",
|
|
131
147
|
keywords: "youtube video 영상 동영상 유튜브",
|
|
132
148
|
icon: Video,
|
|
@@ -136,6 +152,7 @@
|
|
|
136
152
|
},
|
|
137
153
|
},
|
|
138
154
|
{
|
|
155
|
+
feature: "image",
|
|
139
156
|
label: "이미지",
|
|
140
157
|
keywords: "image 이미지 사진 img",
|
|
141
158
|
icon: ImageIcon,
|
|
@@ -145,6 +162,7 @@
|
|
|
145
162
|
},
|
|
146
163
|
},
|
|
147
164
|
{
|
|
165
|
+
feature: "link",
|
|
148
166
|
label: "링크",
|
|
149
167
|
keywords: "link url 링크 하이퍼",
|
|
150
168
|
icon: LinkIcon,
|
|
@@ -163,6 +181,7 @@
|
|
|
163
181
|
|
|
164
182
|
let {
|
|
165
183
|
editor,
|
|
184
|
+
features,
|
|
166
185
|
query,
|
|
167
186
|
onClose,
|
|
168
187
|
onPdfUpload,
|
|
@@ -170,6 +189,7 @@
|
|
|
170
189
|
onVideoUpload,
|
|
171
190
|
}: {
|
|
172
191
|
editor: Editor;
|
|
192
|
+
features: Set<ToolbarFeature>;
|
|
173
193
|
query: string;
|
|
174
194
|
onClose: () => void;
|
|
175
195
|
onPdfUpload?: () => void;
|
|
@@ -182,9 +202,10 @@
|
|
|
182
202
|
let mouseMovedSinceKeyboard = $state(true);
|
|
183
203
|
|
|
184
204
|
const allItems = $derived.by(() => {
|
|
185
|
-
const items =
|
|
205
|
+
const items = SLASH_MENU_ITEMS_DATA.filter((item) => features.has(item.feature));
|
|
186
206
|
if (onPdfUpload) {
|
|
187
207
|
items.push({
|
|
208
|
+
feature: "pdf" as ToolbarFeature,
|
|
188
209
|
label: "PDF 파일",
|
|
189
210
|
keywords: "pdf 파일 문서",
|
|
190
211
|
icon: FileText,
|
|
@@ -193,6 +214,7 @@
|
|
|
193
214
|
}
|
|
194
215
|
if (onVideoUpload) {
|
|
195
216
|
items.push({
|
|
217
|
+
feature: "video" as ToolbarFeature,
|
|
196
218
|
label: "영상 파일",
|
|
197
219
|
keywords: "video 영상 동영상 비디오 메타버스",
|
|
198
220
|
icon: Film,
|
|
@@ -201,6 +223,7 @@
|
|
|
201
223
|
}
|
|
202
224
|
if (onFileUpload) {
|
|
203
225
|
items.push({
|
|
226
|
+
feature: "file" as ToolbarFeature,
|
|
204
227
|
label: "파일 첨부",
|
|
205
228
|
keywords: "file attach 파일 첨부",
|
|
206
229
|
icon: Paperclip,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Editor } from "@tiptap/core";
|
|
2
|
+
import type { ToolbarFeature } from "../types";
|
|
2
3
|
import type { Component } from "svelte";
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
editor: Editor;
|
|
6
|
+
features: Set<ToolbarFeature>;
|
|
5
7
|
query: string;
|
|
6
8
|
onClose: () => void;
|
|
7
9
|
onPdfUpload?: () => void;
|
|
@@ -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;AAsB3C,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,UAAU,CAAC;AAC9D,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,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B,CAAC;AAwSJ,QAAA,MAAM,gBAAgB,qCAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
import BubbleToolbar from "./BubbleToolbar.svelte";
|
|
42
42
|
import SlashCommandMenu from "./SlashCommandMenu.svelte";
|
|
43
43
|
import TableBubbleMenu from "./TableBubbleMenu.svelte";
|
|
44
|
-
import type { UploadHandler, ToolbarMode } from "../types";
|
|
44
|
+
import type { UploadHandler, ToolbarMode, ToolbarFeature } from "../types";
|
|
45
|
+
import { resolveFeatures } from "../types";
|
|
45
46
|
import type { FileResolver } from "../extensions/FileAttachment";
|
|
46
47
|
|
|
47
48
|
const cellAttrs = {
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
extensions: extraExtensions = [],
|
|
116
117
|
editable = true,
|
|
117
118
|
toolbar = 'full',
|
|
119
|
+
features: featuresOverride,
|
|
118
120
|
}: {
|
|
119
121
|
content: string;
|
|
120
122
|
onChange: (html: string) => void;
|
|
@@ -124,8 +126,11 @@
|
|
|
124
126
|
extensions?: AnyExtension[];
|
|
125
127
|
editable?: boolean;
|
|
126
128
|
toolbar?: ToolbarMode;
|
|
129
|
+
features?: ToolbarFeature[];
|
|
127
130
|
} = $props();
|
|
128
131
|
|
|
132
|
+
const features = $derived(resolveFeatures(toolbar, featuresOverride));
|
|
133
|
+
|
|
129
134
|
let editorElement: HTMLDivElement | undefined = $state();
|
|
130
135
|
let editor: Editor | undefined = $state();
|
|
131
136
|
let uploading = $state(false);
|
|
@@ -224,13 +229,19 @@
|
|
|
224
229
|
uploading = true;
|
|
225
230
|
const size = file.size;
|
|
226
231
|
onUploadFile(file)
|
|
227
|
-
.then((
|
|
232
|
+
.then((result) => {
|
|
233
|
+
const isFileId = result && !result.includes("/") && !result.includes(":");
|
|
228
234
|
editor!
|
|
229
235
|
.chain()
|
|
230
236
|
.focus()
|
|
231
237
|
.insertContent({
|
|
232
238
|
type: "fileAttachment",
|
|
233
|
-
attrs: {
|
|
239
|
+
attrs: {
|
|
240
|
+
src: isFileId ? null : result,
|
|
241
|
+
fileId: isFileId ? result : null,
|
|
242
|
+
name: file.name,
|
|
243
|
+
size,
|
|
244
|
+
},
|
|
234
245
|
})
|
|
235
246
|
.run();
|
|
236
247
|
})
|
|
@@ -481,7 +492,10 @@
|
|
|
481
492
|
|
|
482
493
|
// File resolver를 storage에 등록
|
|
483
494
|
if (onResolveFile) {
|
|
484
|
-
editor.storage.fileAttachment = {
|
|
495
|
+
editor.storage.fileAttachment = {
|
|
496
|
+
...editor.storage.fileAttachment,
|
|
497
|
+
resolver: onResolveFile,
|
|
498
|
+
};
|
|
485
499
|
}
|
|
486
500
|
|
|
487
501
|
editor.on("update", handleUpdate);
|
|
@@ -545,13 +559,14 @@
|
|
|
545
559
|
</script>
|
|
546
560
|
|
|
547
561
|
<div
|
|
548
|
-
class="hce-editor-wrapper relative{editable && toolbar
|
|
562
|
+
class="hce-editor-wrapper relative{editable && features.has('fixed-toolbar') ? ' border border-border rounded-xl bg-background' : ''}"
|
|
549
563
|
ondragover={(e) => e.preventDefault()}
|
|
550
564
|
ondrop={(e) => { if (!onUploadFile) e.preventDefault(); }}
|
|
551
565
|
>
|
|
552
|
-
{#if editor && editable && toolbar
|
|
566
|
+
{#if editor && editable && features.has('fixed-toolbar')}
|
|
553
567
|
<FixedToolbar
|
|
554
568
|
{editor}
|
|
569
|
+
{features}
|
|
555
570
|
onPdfClick={() => pdfInputEl?.click()}
|
|
556
571
|
onFileClick={onUploadFile ? () => fileInputEl?.click() : undefined}
|
|
557
572
|
onVideoClick={onUploadFile ? () => videoInputEl?.click() : undefined}
|
|
@@ -561,54 +576,57 @@
|
|
|
561
576
|
<div bind:this={editorElement}></div>
|
|
562
577
|
|
|
563
578
|
{#if editor && editable}
|
|
564
|
-
|
|
579
|
+
{#if features.has('bubble-toolbar')}
|
|
580
|
+
<BubbleToolbar {editor} {features} />
|
|
581
|
+
{/if}
|
|
565
582
|
|
|
566
|
-
{#if
|
|
583
|
+
{#if features.has('table-menu')}
|
|
567
584
|
<TableBubbleMenu {editor} />
|
|
568
585
|
{/if}
|
|
569
586
|
|
|
570
|
-
{#if
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
>
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
</div>
|
|
579
|
-
{/if}
|
|
587
|
+
{#if features.has('upload-overlay') && uploading}
|
|
588
|
+
<div
|
|
589
|
+
class="absolute inset-0 flex items-center justify-center bg-background/60 rounded-xl"
|
|
590
|
+
>
|
|
591
|
+
<p class="text-sm text-muted-foreground animate-pulse">
|
|
592
|
+
업로드 중...
|
|
593
|
+
</p>
|
|
594
|
+
</div>
|
|
580
595
|
{/if}
|
|
581
596
|
|
|
582
|
-
{#if
|
|
597
|
+
{#if features.has('slash-menu') && slashMenuOpen}
|
|
583
598
|
<div
|
|
584
599
|
style="top: {slashMenuPos.top}px; left: {slashMenuPos.left}px"
|
|
585
600
|
class="fixed z-50"
|
|
586
601
|
>
|
|
587
602
|
<SlashCommandMenu
|
|
588
603
|
{editor}
|
|
604
|
+
{features}
|
|
589
605
|
query={slashQuery}
|
|
590
606
|
onClose={closeSlashMenu}
|
|
591
|
-
onPdfUpload={onUploadFile
|
|
607
|
+
onPdfUpload={onUploadFile && features.has('pdf')
|
|
592
608
|
? () => pdfInputEl?.click()
|
|
593
609
|
: undefined}
|
|
594
|
-
onFileUpload={onUploadFile
|
|
610
|
+
onFileUpload={onUploadFile && features.has('file')
|
|
595
611
|
? () => fileInputEl?.click()
|
|
596
612
|
: undefined}
|
|
597
|
-
onVideoUpload={onUploadFile
|
|
613
|
+
onVideoUpload={onUploadFile && features.has('video')
|
|
598
614
|
? () => videoInputEl?.click()
|
|
599
615
|
: undefined}
|
|
600
616
|
/>
|
|
601
617
|
</div>
|
|
602
618
|
{/if}
|
|
603
619
|
|
|
604
|
-
{#if
|
|
620
|
+
{#if features.has('character-count')}
|
|
605
621
|
<div
|
|
606
622
|
class="flex justify-end px-4 py-2 text-xs text-muted-foreground border-t border-border"
|
|
607
623
|
>
|
|
608
624
|
{editor.storage.characterCount.characters()} 자 ·
|
|
609
625
|
{editor.storage.characterCount.words()} 단어
|
|
610
626
|
</div>
|
|
627
|
+
{/if}
|
|
611
628
|
|
|
629
|
+
{#if features.has('pdf') || features.has('file') || features.has('video')}
|
|
612
630
|
<input
|
|
613
631
|
bind:this={pdfInputEl}
|
|
614
632
|
type="file"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnyExtension } from "@tiptap/core";
|
|
2
|
-
import type { UploadHandler, ToolbarMode } from "../types";
|
|
2
|
+
import type { UploadHandler, ToolbarMode, ToolbarFeature } from "../types";
|
|
3
3
|
import type { FileResolver } from "../extensions/FileAttachment";
|
|
4
4
|
type $$ComponentProps = {
|
|
5
5
|
content: string;
|
|
@@ -10,6 +10,7 @@ type $$ComponentProps = {
|
|
|
10
10
|
extensions?: AnyExtension[];
|
|
11
11
|
editable?: boolean;
|
|
12
12
|
toolbar?: ToolbarMode;
|
|
13
|
+
features?: ToolbarFeature[];
|
|
13
14
|
};
|
|
14
15
|
declare const TipTapEditor: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
16
|
type TipTapEditor = ReturnType<typeof TipTapEditor>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAa5D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"TipTapEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.svelte.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAa5D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE3E,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,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;AA2mBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileAttachment.d.ts","sourceRoot":"","sources":["../../src/extensions/FileAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE1E,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,cAAc,EAAE;YACd,iBAAiB,EAAE,CAAC,KAAK,EAAE;gBACzB,GAAG,CAAC,EAAE,MAAM,CAAC;gBACb,MAAM,CAAC,EAAE,MAAM,CAAC;gBAChB,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,KAAK,UAAU,CAAC;SAClB,CAAC;KACH;CACF;
|
|
1
|
+
{"version":3,"file":"FileAttachment.d.ts","sourceRoot":"","sources":["../../src/extensions/FileAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE1E,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,cAAc,EAAE;YACd,iBAAiB,EAAE,CAAC,KAAK,EAAE;gBACzB,GAAG,CAAC,EAAE,MAAM,CAAC;gBACb,MAAM,CAAC,EAAE,MAAM,CAAC;gBAChB,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,CAAC,EAAE,MAAM,CAAC;aACf,KAAK,UAAU,CAAC;SAClB,CAAC;KACH;CACF;AAkCD,eAAO,MAAM,cAAc,kCAmNzB,CAAC"}
|
|
@@ -6,6 +6,10 @@ function formatFileSize(bytes) {
|
|
|
6
6
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
7
7
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8
8
|
}
|
|
9
|
+
function isInlineable(name) {
|
|
10
|
+
const ext = name.split(".").pop()?.toLowerCase() || "";
|
|
11
|
+
return ["pdf", "png", "jpg", "jpeg", "gif", "webp", "svg"].includes(ext);
|
|
12
|
+
}
|
|
9
13
|
function getFileIcon(name) {
|
|
10
14
|
const ext = name.split(".").pop()?.toLowerCase() || "";
|
|
11
15
|
const icons = {
|
|
@@ -111,16 +115,47 @@ export const FileAttachment = Node.create({
|
|
|
111
115
|
const info = document.createElement("div");
|
|
112
116
|
info.style.cssText = "flex:1;min-width:0;";
|
|
113
117
|
const nameEl = document.createElement("a");
|
|
114
|
-
nameEl.target = "_blank";
|
|
115
|
-
nameEl.rel = "noopener noreferrer";
|
|
116
118
|
nameEl.textContent = node.attrs.name;
|
|
117
119
|
nameEl.style.cssText =
|
|
118
|
-
"display:block;font-size:13px;font-weight:600;color:var(--primary, #4A7DAC);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
120
|
+
"display:block;font-size:13px;font-weight:600;color:var(--primary, #4A7DAC);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;";
|
|
121
|
+
let resolvedSrc = null;
|
|
122
|
+
let resolvedName = node.attrs.name;
|
|
123
|
+
function getProxyUrl() {
|
|
124
|
+
const fileId = node.attrs.fileId;
|
|
125
|
+
if (!fileId)
|
|
126
|
+
return null;
|
|
127
|
+
const baseUrl = editor.storage.fileAttachment?.downloadBaseUrl || "/api/upload";
|
|
128
|
+
return `${baseUrl}/${fileId}/download`;
|
|
129
|
+
}
|
|
130
|
+
function handleClick(e) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
const proxyUrl = getProxyUrl();
|
|
133
|
+
const targetUrl = proxyUrl || resolvedSrc;
|
|
134
|
+
if (!targetUrl)
|
|
135
|
+
return;
|
|
136
|
+
if (isInlineable(resolvedName)) {
|
|
137
|
+
window.open(targetUrl, "_blank");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
fetch(targetUrl)
|
|
141
|
+
.then((res) => res.blob())
|
|
142
|
+
.then((blob) => {
|
|
143
|
+
const url = URL.createObjectURL(blob);
|
|
144
|
+
const a = document.createElement("a");
|
|
145
|
+
a.href = url;
|
|
146
|
+
a.download = resolvedName;
|
|
147
|
+
a.click();
|
|
148
|
+
URL.revokeObjectURL(url);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
nameEl.addEventListener("click", handleClick);
|
|
119
153
|
const sizeEl = document.createElement("span");
|
|
120
154
|
sizeEl.style.cssText =
|
|
121
155
|
"font-size:11px;color:var(--muted-foreground, #718096);";
|
|
122
156
|
// URL이 있으면 바로 표시
|
|
123
157
|
if (node.attrs.src) {
|
|
158
|
+
resolvedSrc = node.attrs.src;
|
|
124
159
|
nameEl.href = node.attrs.src;
|
|
125
160
|
if (node.attrs.size)
|
|
126
161
|
sizeEl.textContent = formatFileSize(node.attrs.size);
|
|
@@ -134,9 +169,11 @@ export const FileAttachment = Node.create({
|
|
|
134
169
|
if (resolver) {
|
|
135
170
|
resolver(node.attrs.fileId)
|
|
136
171
|
.then((result) => {
|
|
172
|
+
resolvedSrc = result.src;
|
|
137
173
|
nameEl.href = result.src;
|
|
138
174
|
nameEl.style.pointerEvents = "";
|
|
139
175
|
if (result.name) {
|
|
176
|
+
resolvedName = result.name;
|
|
140
177
|
nameEl.textContent = result.name;
|
|
141
178
|
icon.textContent = getFileIcon(result.name);
|
|
142
179
|
}
|
|
@@ -178,7 +215,10 @@ export const FileAttachment = Node.create({
|
|
|
178
215
|
};
|
|
179
216
|
},
|
|
180
217
|
addStorage() {
|
|
181
|
-
return {
|
|
218
|
+
return {
|
|
219
|
+
resolver: null,
|
|
220
|
+
downloadBaseUrl: "/api/upload",
|
|
221
|
+
};
|
|
182
222
|
},
|
|
183
223
|
addCommands() {
|
|
184
224
|
return {
|
|
@@ -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;AAInE,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"PdfBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/PdfBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,UAAU,EAAmB,MAAM,cAAc,CAAC;AAInE,eAAO,MAAM,QAAQ,sBA8TnB,CAAC"}
|
|
@@ -245,31 +245,36 @@ export const PdfBlock = TiptapNode.create({
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
// URL 해결 후 PDF 로드
|
|
248
|
+
function getProxyUrl(fileId) {
|
|
249
|
+
const baseUrl = editor.storage.fileAttachment?.downloadBaseUrl || "/api/upload";
|
|
250
|
+
return `${baseUrl}/${fileId}/download`;
|
|
251
|
+
}
|
|
248
252
|
if (node.attrs.src) {
|
|
249
|
-
|
|
250
|
-
|
|
253
|
+
if (node.attrs.fileId) {
|
|
254
|
+
// fileId 있으면 프록시 URL 사용 (경로 숨김)
|
|
255
|
+
const proxyUrl = getProxyUrl(node.attrs.fileId);
|
|
256
|
+
openLink.href = proxyUrl;
|
|
257
|
+
loadPdf(proxyUrl);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
openLink.href = node.attrs.src;
|
|
261
|
+
loadPdf(node.attrs.src);
|
|
262
|
+
}
|
|
251
263
|
}
|
|
252
264
|
else if (node.attrs.fileId) {
|
|
253
|
-
|
|
254
|
-
openLink.href =
|
|
265
|
+
const proxyUrl = getProxyUrl(node.attrs.fileId);
|
|
266
|
+
openLink.href = proxyUrl;
|
|
267
|
+
// resolver로 이름 획득
|
|
255
268
|
const resolver = editor.storage.fileAttachment?.resolver;
|
|
256
269
|
if (resolver) {
|
|
257
270
|
resolver(node.attrs.fileId)
|
|
258
271
|
.then((result) => {
|
|
259
|
-
openLink.href = result.src;
|
|
260
272
|
if (result.name)
|
|
261
273
|
nameSpan.textContent = result.name;
|
|
262
|
-
loadPdf(result.src);
|
|
263
274
|
})
|
|
264
|
-
.catch(() => {
|
|
265
|
-
loadingDiv.innerHTML =
|
|
266
|
-
'<p class="text-sm text-muted-foreground">PDF URL\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.</p>';
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
loadingDiv.innerHTML =
|
|
271
|
-
`<p class="text-sm text-muted-foreground">PDF ID: ${node.attrs.fileId}</p>`;
|
|
275
|
+
.catch(() => { });
|
|
272
276
|
}
|
|
277
|
+
loadPdf(proxyUrl);
|
|
273
278
|
}
|
|
274
279
|
return {
|
|
275
280
|
dom,
|
package/dist/index.d.ts
CHANGED
|
@@ -16,5 +16,6 @@ export { Column } from "./extensions/Column";
|
|
|
16
16
|
export { sanitizeHtml, stripHtmlToExcerpt, transformLegacyHtml } from "./utils/sanitize";
|
|
17
17
|
export { configurePdfJs, getPdfJs } from "./utils/pdf";
|
|
18
18
|
export { cn } from "./utils/cn";
|
|
19
|
-
export type { TipTapEditorProps, UploadHandler, ToolbarMode, FixedToolbarProps, BlockHandleProps, SlashCommandMenuProps, PdfViewerProps, SlashMenuItem, } from "./types";
|
|
19
|
+
export type { TipTapEditorProps, UploadHandler, ToolbarMode, ToolbarFeature, FixedToolbarProps, BlockHandleProps, SlashCommandMenuProps, PdfViewerProps, SlashMenuItem, } from "./types";
|
|
20
|
+
export { TOOLBAR_PRESETS, resolveFeatures } from "./types";
|
|
20
21
|
//# sourceMappingURL=index.d.ts.map
|
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,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAG7C,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,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,aAAa,GACd,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,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAG7C,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"}
|
package/dist/index.js
CHANGED
|
@@ -18,3 +18,4 @@ export { Column } from "./extensions/Column";
|
|
|
18
18
|
export { sanitizeHtml, stripHtmlToExcerpt, transformLegacyHtml } from "./utils/sanitize";
|
|
19
19
|
export { configurePdfJs, getPdfJs } from "./utils/pdf";
|
|
20
20
|
export { cn } from "./utils/cn";
|
|
21
|
+
export { TOOLBAR_PRESETS, resolveFeatures } from "./types";
|