@seorii/tiptap 0.3.0-next.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n/index.d.ts +106 -6
- package/dist/i18n/index.js +56 -11
- package/dist/plugin/command/emoji.d.ts +3 -17
- package/dist/plugin/command/emoji.js +51 -24
- package/dist/plugin/command/stores.svelte.d.ts +52 -0
- package/dist/plugin/command/stores.svelte.js +69 -0
- package/dist/plugin/command/suggest.d.ts +6 -19
- package/dist/plugin/command/suggest.js +133 -51
- package/dist/plugin/embed.d.ts +2 -2
- package/dist/plugin/embed.js +5 -1
- package/dist/plugin/image/dragdrop.d.ts +2 -0
- package/dist/plugin/image/dragdrop.js +66 -2
- package/dist/plugin/image/index.js +3 -2
- package/dist/plugin/indent.js +0 -1
- package/dist/plugin/orderedlist/index.d.ts +1 -1
- package/dist/plugin/orderedlist/index.js +1 -1
- package/dist/plugin/orderedlist/{korean.scss → korean.css} +2 -2
- package/dist/plugin/table/index.d.ts +1 -1
- package/dist/plugin/table/index.js +19 -11
- package/dist/plugin/table/style/{cell.scss → cell.css} +6 -5
- package/dist/plugin/table/style/{grip.scss → grip.css} +14 -19
- package/dist/plugin/table/style/resize.css +28 -0
- package/dist/plugin/table/style/{table.scss → table.css} +15 -17
- package/dist/plugin/table/style.css +4 -0
- package/dist/plugin/table/tableCell/index.js +2 -4
- package/dist/plugin/table/tableHeader/index.js +1 -2
- package/dist/tiptap/Bubble.svelte +106 -71
- package/dist/tiptap/Bubble.svelte.d.ts +8 -6
- package/dist/tiptap/Command.svelte +160 -158
- package/dist/tiptap/Command.svelte.d.ts +2 -3
- package/dist/tiptap/Floating.svelte +51 -24
- package/dist/tiptap/Floating.svelte.d.ts +1 -0
- package/dist/tiptap/TipTap.svelte +215 -135
- package/dist/tiptap/TipTap.svelte.d.ts +7 -3
- package/dist/tiptap/ToolbarButton.svelte +30 -10
- package/dist/tiptap/ToolbarButton.svelte.d.ts +10 -6
- package/dist/tiptap/setMath.d.ts +2 -1
- package/dist/tiptap/setMath.js +74 -12
- package/dist/tiptap/tiptap.d.ts +9 -1
- package/dist/tiptap/tiptap.js +170 -16
- package/package.json +63 -57
- package/dist/plugin/command/stores.d.ts +0 -13
- package/dist/plugin/command/stores.js +0 -7
- package/dist/plugin/table/style/resize.scss +0 -26
- package/dist/plugin/table/style.scss +0 -4
|
@@ -1,18 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeSlash, moveSlashSelection, runSlashItemAt, setSlashItems, setSlashLocation, setSlashProps, slashState } from './stores.svelte';
|
|
2
2
|
import i18n from '../../i18n';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import enUs from '../../i18n/en-us/index';
|
|
4
|
+
import koKr from '../../i18n/ko-kr/index';
|
|
5
|
+
import { fallbackUpload, releaseObjectUrlOnImageSettled } from '../image/dragdrop';
|
|
6
|
+
import { PluginKey, TextSelection } from '@tiptap/pm/state';
|
|
7
|
+
import Suggestion, {} from '@tiptap/suggestion';
|
|
8
|
+
const normalizeSearch = (value) => value.toLowerCase().trim();
|
|
9
|
+
const compactSearch = (value) => normalizeSearch(value).replace(/\s+/g, '');
|
|
10
|
+
function localeText(key, locale) {
|
|
11
|
+
const value = locale[key];
|
|
12
|
+
return typeof value === 'string' ? value : '';
|
|
13
|
+
}
|
|
14
|
+
function createKeywords(keys, extra = []) {
|
|
15
|
+
return [
|
|
16
|
+
...new Set([...extra, ...keys.flatMap((key) => [localeText(key, enUs), localeText(key, koKr)])]
|
|
17
|
+
.map(normalizeSearch)
|
|
18
|
+
.filter(Boolean))
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function matchQuery(value, query, compactQuery) {
|
|
22
|
+
const normalizedValue = normalizeSearch(value);
|
|
23
|
+
return normalizedValue.includes(query) || compactSearch(normalizedValue).includes(compactQuery);
|
|
24
|
+
}
|
|
25
|
+
function matchItem(item, query, compactQuery) {
|
|
26
|
+
if (!query)
|
|
27
|
+
return true;
|
|
28
|
+
return [item.title, item.subtitle ?? '', ...(item.keywords ?? [])].some((value) => matchQuery(value, query, compactQuery));
|
|
29
|
+
}
|
|
30
|
+
function fixRange(editor, rawRange, split = '/') {
|
|
31
|
+
const range = { ...rawRange };
|
|
32
|
+
const { state } = editor.view;
|
|
33
|
+
const { selection, doc } = state;
|
|
9
34
|
if (selection.$to.nodeBefore?.text?.includes?.(split)) {
|
|
10
35
|
range.from = range.to;
|
|
11
36
|
while (range.from > 0 && doc.textBetween(range.from - 1, range.from) !== split) {
|
|
12
37
|
try {
|
|
13
38
|
range.from -= 1;
|
|
14
39
|
}
|
|
15
|
-
catch
|
|
40
|
+
catch {
|
|
16
41
|
range.from += 2;
|
|
17
42
|
break;
|
|
18
43
|
}
|
|
@@ -23,29 +48,35 @@ function fixRange(editor, range, split = '/') {
|
|
|
23
48
|
try {
|
|
24
49
|
range.to += 1;
|
|
25
50
|
}
|
|
26
|
-
catch
|
|
51
|
+
catch {
|
|
27
52
|
range.to -= 1;
|
|
28
53
|
break;
|
|
29
54
|
}
|
|
30
55
|
}
|
|
31
56
|
return range;
|
|
32
57
|
}
|
|
33
|
-
export function getDetail(editor, range,
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
export function getDetail(editor, range, option) {
|
|
59
|
+
slashState.selection = () => {
|
|
60
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).run();
|
|
61
|
+
};
|
|
62
|
+
slashState.detail = option;
|
|
36
63
|
}
|
|
37
64
|
export const suggest = {
|
|
38
65
|
pluginKey: new PluginKey('slash-suggest'),
|
|
39
66
|
char: '/',
|
|
40
67
|
items: ({ query }) => {
|
|
68
|
+
const blocks = typeof window !== 'undefined'
|
|
69
|
+
? (window.__tiptap_blocks ?? [])
|
|
70
|
+
: [];
|
|
41
71
|
const raw = [
|
|
42
72
|
{
|
|
43
73
|
section: i18n('text'),
|
|
44
74
|
list: [
|
|
45
75
|
{
|
|
46
76
|
icon: 'title',
|
|
47
|
-
title: i18n('title')
|
|
77
|
+
title: `${i18n('title')} 1`,
|
|
48
78
|
subtitle: i18n('title1Info'),
|
|
79
|
+
keywords: createKeywords(['title', 'title1Info'], ['heading 1', 'h1']),
|
|
49
80
|
command: ({ editor, range }) => {
|
|
50
81
|
editor
|
|
51
82
|
.chain()
|
|
@@ -57,8 +88,9 @@ export const suggest = {
|
|
|
57
88
|
},
|
|
58
89
|
{
|
|
59
90
|
icon: 'title',
|
|
60
|
-
title: i18n('title')
|
|
91
|
+
title: `${i18n('title')} 2`,
|
|
61
92
|
subtitle: i18n('title2Info'),
|
|
93
|
+
keywords: createKeywords(['title', 'title2Info'], ['heading 2', 'h2']),
|
|
62
94
|
command: ({ editor, range }) => {
|
|
63
95
|
editor
|
|
64
96
|
.chain()
|
|
@@ -70,8 +102,9 @@ export const suggest = {
|
|
|
70
102
|
},
|
|
71
103
|
{
|
|
72
104
|
icon: 'title',
|
|
73
|
-
title: i18n('title')
|
|
105
|
+
title: `${i18n('title')} 3`,
|
|
74
106
|
subtitle: i18n('title3Info'),
|
|
107
|
+
keywords: createKeywords(['title', 'title3Info'], ['heading 3', 'h3']),
|
|
75
108
|
command: ({ editor, range }) => {
|
|
76
109
|
editor
|
|
77
110
|
.chain()
|
|
@@ -85,6 +118,7 @@ export const suggest = {
|
|
|
85
118
|
icon: 'format_list_bulleted',
|
|
86
119
|
title: i18n('unorderedList'),
|
|
87
120
|
subtitle: i18n('unorderedListInfo'),
|
|
121
|
+
keywords: createKeywords(['unorderedList', 'unorderedListInfo'], ['bullet list', 'ul']),
|
|
88
122
|
command: ({ editor, range }) => {
|
|
89
123
|
editor.commands.deleteRange(fixRange(editor, range));
|
|
90
124
|
editor.commands.toggleBulletList();
|
|
@@ -94,6 +128,7 @@ export const suggest = {
|
|
|
94
128
|
icon: 'format_list_numbered',
|
|
95
129
|
title: i18n('numberList'),
|
|
96
130
|
subtitle: i18n('numberListInfo'),
|
|
131
|
+
keywords: createKeywords(['numberList', 'numberListInfo'], ['ordered list', 'ol']),
|
|
97
132
|
command: ({ editor, range }) => {
|
|
98
133
|
editor.commands.deleteRange(fixRange(editor, range));
|
|
99
134
|
editor.commands.toggleOrderedList();
|
|
@@ -104,25 +139,27 @@ export const suggest = {
|
|
|
104
139
|
{
|
|
105
140
|
section: i18n('block'),
|
|
106
141
|
list: [
|
|
107
|
-
...
|
|
142
|
+
...blocks,
|
|
108
143
|
{
|
|
109
144
|
icon: 'image',
|
|
110
145
|
title: i18n('image'),
|
|
111
146
|
subtitle: i18n('imageInfo'),
|
|
147
|
+
keywords: createKeywords(['image', 'imageInfo']),
|
|
112
148
|
command: ({ editor, range }) => {
|
|
113
149
|
editor.chain().focus().deleteRange(fixRange(editor, range)).run();
|
|
114
150
|
const input = document.createElement('input');
|
|
115
151
|
input.type = 'file';
|
|
116
152
|
input.accept = 'image/*';
|
|
117
153
|
input.onchange = async () => {
|
|
118
|
-
if (input.files)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
154
|
+
if (!input.files?.length)
|
|
155
|
+
return;
|
|
156
|
+
const file = input.files[0];
|
|
157
|
+
if (!file)
|
|
158
|
+
return;
|
|
159
|
+
const upload = window.__image_uploader ?? fallbackUpload;
|
|
160
|
+
const src = await upload(file);
|
|
161
|
+
editor.chain().focus().deleteRange(range).setImage({ src }).run();
|
|
162
|
+
releaseObjectUrlOnImageSettled(editor.view, src);
|
|
126
163
|
};
|
|
127
164
|
input.click();
|
|
128
165
|
}
|
|
@@ -131,24 +168,42 @@ export const suggest = {
|
|
|
131
168
|
icon: 'code',
|
|
132
169
|
title: i18n('codeBlock'),
|
|
133
170
|
subtitle: i18n('codeBlockInfo'),
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
keywords: createKeywords(['codeBlock', 'codeBlockInfo'], ['code']),
|
|
172
|
+
command: ({ editor, range }) => {
|
|
173
|
+
editor
|
|
174
|
+
.chain()
|
|
175
|
+
.focus()
|
|
176
|
+
.deleteRange(fixRange(editor, range))
|
|
177
|
+
.setNode('codeBlock', { language: null })
|
|
178
|
+
.command(({ tr }) => {
|
|
179
|
+
const { from } = tr.selection;
|
|
180
|
+
const $from = tr.doc.resolve(from);
|
|
181
|
+
if ($from.parent.type.name === 'codeBlock') {
|
|
182
|
+
tr.setSelection(TextSelection.create(tr.doc, $from.start()));
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
const before = $from.nodeBefore;
|
|
186
|
+
if (before?.type.name === 'codeBlock') {
|
|
187
|
+
const positionInsideCodeBlock = from - before.nodeSize + 1;
|
|
188
|
+
tr.setSelection(TextSelection.create(tr.doc, positionInsideCodeBlock));
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
const after = $from.nodeAfter;
|
|
192
|
+
if (after?.type.name === 'codeBlock') {
|
|
193
|
+
tr.setSelection(TextSelection.create(tr.doc, from + 1));
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
})
|
|
197
|
+
.focus()
|
|
198
|
+
.run();
|
|
199
|
+
}
|
|
145
200
|
},
|
|
146
201
|
{
|
|
147
202
|
icon: 'functions',
|
|
148
203
|
title: i18n('mathBlock'),
|
|
149
204
|
subtitle: i18n('mathBlockInfo'),
|
|
205
|
+
keywords: createKeywords(['mathBlock', 'mathBlockInfo'], ['latex', 'equation']),
|
|
150
206
|
command: ({ editor, range }) => {
|
|
151
|
-
const { to } = range;
|
|
152
207
|
editor
|
|
153
208
|
.chain()
|
|
154
209
|
.focus()
|
|
@@ -162,6 +217,7 @@ export const suggest = {
|
|
|
162
217
|
icon: 'table_chart',
|
|
163
218
|
title: i18n('table'),
|
|
164
219
|
subtitle: i18n('tableInfo'),
|
|
220
|
+
keywords: createKeywords(['table', 'tableInfo']),
|
|
165
221
|
command: ({ editor, range }) => {
|
|
166
222
|
editor
|
|
167
223
|
.chain()
|
|
@@ -178,6 +234,7 @@ export const suggest = {
|
|
|
178
234
|
icon: 'format_quote',
|
|
179
235
|
title: i18n('blockquote'),
|
|
180
236
|
subtitle: i18n('blockquoteInfo'),
|
|
237
|
+
keywords: createKeywords(['blockquote', 'blockquoteInfo'], ['quote']),
|
|
181
238
|
command: ({ editor, range }) => {
|
|
182
239
|
editor
|
|
183
240
|
.chain()
|
|
@@ -192,6 +249,7 @@ export const suggest = {
|
|
|
192
249
|
icon: 'iframe',
|
|
193
250
|
title: i18n('iframe'),
|
|
194
251
|
subtitle: i18n('iframeInfo'),
|
|
252
|
+
keywords: createKeywords(['iframe', 'iframeInfo'], ['embed', 'url']),
|
|
195
253
|
command: ({ editor, range }) => getDetail(editor, range, {
|
|
196
254
|
title: 'iframe',
|
|
197
255
|
placeholder: 'url',
|
|
@@ -214,6 +272,7 @@ export const suggest = {
|
|
|
214
272
|
icon: 'youtube_activity',
|
|
215
273
|
title: i18n('youtube'),
|
|
216
274
|
subtitle: i18n('youtubeInfo'),
|
|
275
|
+
keywords: createKeywords(['youtube', 'youtubeInfo'], ['video']),
|
|
217
276
|
command: ({ editor, range }) => getDetail(editor, range, {
|
|
218
277
|
title: 'youtube',
|
|
219
278
|
placeholder: 'url',
|
|
@@ -230,38 +289,61 @@ export const suggest = {
|
|
|
230
289
|
]
|
|
231
290
|
}
|
|
232
291
|
];
|
|
233
|
-
const
|
|
292
|
+
const normalizedQuery = normalizeSearch(query);
|
|
293
|
+
const compactQuery = compactSearch(query);
|
|
294
|
+
return raw
|
|
234
295
|
.map(({ section, list }) => ({
|
|
235
296
|
section,
|
|
236
|
-
list: list.filter((item) => item
|
|
237
|
-
item.subtitle.toLowerCase().includes(query.toLowerCase()))
|
|
297
|
+
list: list.filter((item) => matchItem(item, normalizedQuery, compactQuery))
|
|
238
298
|
}))
|
|
239
299
|
.filter(({ list }) => list.length > 0);
|
|
240
|
-
return filtered;
|
|
241
300
|
},
|
|
242
301
|
render: () => {
|
|
243
302
|
return {
|
|
244
303
|
onStart: (props) => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
304
|
+
const { editor, range } = props;
|
|
305
|
+
setSlashProps({ editor, range });
|
|
306
|
+
slashState.visible = true;
|
|
307
|
+
slashState.selectedIndex = 0;
|
|
308
|
+
setSlashItems(props.items);
|
|
309
|
+
slashState.detail = null;
|
|
310
|
+
const location = props.clientRect?.();
|
|
311
|
+
if (location) {
|
|
312
|
+
setSlashLocation({ x: location.x, y: location.y, height: location.height });
|
|
313
|
+
}
|
|
253
314
|
},
|
|
254
315
|
onUpdate(props) {
|
|
255
|
-
|
|
316
|
+
setSlashProps({ editor: props.editor, range: props.range });
|
|
317
|
+
setSlashItems(props.items);
|
|
256
318
|
},
|
|
257
319
|
onKeyDown(props) {
|
|
320
|
+
if (props.event.key === 'ArrowUp') {
|
|
321
|
+
props.event.preventDefault();
|
|
322
|
+
moveSlashSelection(-1);
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (props.event.key === 'ArrowDown') {
|
|
326
|
+
props.event.preventDefault();
|
|
327
|
+
moveSlashSelection(1);
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
if (props.event.key === 'Tab') {
|
|
331
|
+
props.event.preventDefault();
|
|
332
|
+
moveSlashSelection(props.event.shiftKey ? -1 : 1);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
if (props.event.key === 'Enter') {
|
|
336
|
+
props.event.preventDefault();
|
|
337
|
+
return runSlashItemAt(slashState.selectedIndex);
|
|
338
|
+
}
|
|
258
339
|
if (props.event.key === 'Escape') {
|
|
259
|
-
|
|
340
|
+
closeSlash();
|
|
260
341
|
return true;
|
|
261
342
|
}
|
|
343
|
+
return false;
|
|
262
344
|
},
|
|
263
345
|
onExit() {
|
|
264
|
-
|
|
346
|
+
closeSlash();
|
|
265
347
|
}
|
|
266
348
|
};
|
|
267
349
|
}
|
package/dist/plugin/embed.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Node } from '@tiptap/core';
|
|
2
2
|
export interface EmbedOptions {
|
|
3
3
|
allowFullscreen: boolean;
|
|
4
|
-
type: string;
|
|
5
4
|
HTMLAttributes: {
|
|
6
5
|
[key: string]: any;
|
|
7
6
|
};
|
|
@@ -12,7 +11,8 @@ declare module '@tiptap/core' {
|
|
|
12
11
|
setEmbed: (options: {
|
|
13
12
|
src: string;
|
|
14
13
|
type: string;
|
|
15
|
-
width
|
|
14
|
+
width?: string;
|
|
15
|
+
height?: string;
|
|
16
16
|
}) => ReturnType;
|
|
17
17
|
};
|
|
18
18
|
}
|
package/dist/plugin/embed.js
CHANGED
|
@@ -42,7 +42,11 @@ export default Node.create({
|
|
|
42
42
|
return {
|
|
43
43
|
setEmbed: (options) => ({ tr, dispatch }) => {
|
|
44
44
|
const { selection } = tr;
|
|
45
|
-
const node = this.type.create(
|
|
45
|
+
const node = this.type.create({
|
|
46
|
+
width: options.width ?? '100%',
|
|
47
|
+
height: options.height ?? '800px',
|
|
48
|
+
...options
|
|
49
|
+
});
|
|
46
50
|
if (dispatch)
|
|
47
51
|
tr.replaceRangeWith(selection.from, selection.to, node);
|
|
48
52
|
return true;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
import type { EditorView } from 'prosemirror-view';
|
|
2
3
|
export type UploadFn = (image: File) => Promise<string>;
|
|
3
4
|
export declare const fallbackUpload: (image: File) => Promise<string>;
|
|
5
|
+
export declare const releaseObjectUrlOnImageSettled: (view: EditorView, src: string) => void;
|
|
4
6
|
export declare const dropImagePlugin: () => Plugin<any>;
|
|
@@ -1,5 +1,66 @@
|
|
|
1
|
-
import { Plugin
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
2
|
export const fallbackUpload = async (image) => URL.createObjectURL(image);
|
|
3
|
+
const OBJECT_URL_PREFIX = 'blob:';
|
|
4
|
+
const OBJECT_URL_REVOKE_TIMEOUT_MS = 30_000;
|
|
5
|
+
const isObjectUrl = (src) => src.startsWith(OBJECT_URL_PREFIX);
|
|
6
|
+
const revokeObjectUrl = (src) => {
|
|
7
|
+
try {
|
|
8
|
+
URL.revokeObjectURL(src);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// no-op
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const releaseObjectUrlOnImageSettled = (view, src) => {
|
|
15
|
+
if (!isObjectUrl(src))
|
|
16
|
+
return;
|
|
17
|
+
let released = false;
|
|
18
|
+
const cleanups = [];
|
|
19
|
+
const release = () => {
|
|
20
|
+
if (released)
|
|
21
|
+
return;
|
|
22
|
+
released = true;
|
|
23
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
24
|
+
cleanups.length = 0;
|
|
25
|
+
revokeObjectUrl(src);
|
|
26
|
+
};
|
|
27
|
+
const timer = setTimeout(release, OBJECT_URL_REVOKE_TIMEOUT_MS);
|
|
28
|
+
cleanups.push(() => clearTimeout(timer));
|
|
29
|
+
const bind = () => {
|
|
30
|
+
const images = Array.from(view.dom.querySelectorAll('img')).filter((image) => image.getAttribute('src') === src);
|
|
31
|
+
if (!images.length)
|
|
32
|
+
return false;
|
|
33
|
+
let pending = 0;
|
|
34
|
+
images.forEach((image) => {
|
|
35
|
+
if (image.complete)
|
|
36
|
+
return;
|
|
37
|
+
pending += 1;
|
|
38
|
+
const settle = () => {
|
|
39
|
+
image.removeEventListener('load', settle);
|
|
40
|
+
image.removeEventListener('error', settle);
|
|
41
|
+
pending -= 1;
|
|
42
|
+
if (pending <= 0)
|
|
43
|
+
release();
|
|
44
|
+
};
|
|
45
|
+
image.addEventListener('load', settle);
|
|
46
|
+
image.addEventListener('error', settle);
|
|
47
|
+
cleanups.push(() => {
|
|
48
|
+
image.removeEventListener('load', settle);
|
|
49
|
+
image.removeEventListener('error', settle);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
if (pending <= 0)
|
|
53
|
+
release();
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
queueMicrotask(() => {
|
|
57
|
+
if (bind())
|
|
58
|
+
return;
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
bind();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
};
|
|
3
64
|
export const dropImagePlugin = () => {
|
|
4
65
|
return new Plugin({
|
|
5
66
|
props: {
|
|
@@ -19,6 +80,7 @@ export const dropImagePlugin = () => {
|
|
|
19
80
|
});
|
|
20
81
|
const transaction = view.state.tr.replaceSelectionWith(node);
|
|
21
82
|
view.dispatch(transaction);
|
|
83
|
+
releaseObjectUrlOnImageSettled(view, src);
|
|
22
84
|
});
|
|
23
85
|
}
|
|
24
86
|
}
|
|
@@ -59,11 +121,13 @@ export const dropImagePlugin = () => {
|
|
|
59
121
|
images.forEach(async (image) => {
|
|
60
122
|
const reader = new FileReader();
|
|
61
123
|
if (upload) {
|
|
124
|
+
const src = await upload(image);
|
|
62
125
|
const node = schema.nodes.image.create({
|
|
63
|
-
src
|
|
126
|
+
src
|
|
64
127
|
});
|
|
65
128
|
const transaction = view.state.tr.insert(coordinates.pos, node);
|
|
66
129
|
view.dispatch(transaction);
|
|
130
|
+
releaseObjectUrlOnImageSettled(view, src);
|
|
67
131
|
}
|
|
68
132
|
else {
|
|
69
133
|
reader.onload = (readerEvent) => {
|
|
@@ -3,14 +3,15 @@ import { mergeAttributes } from '@tiptap/core';
|
|
|
3
3
|
import { dropImagePlugin } from './dragdrop';
|
|
4
4
|
export default (crossorigin = 'anonymous') => Image.extend({
|
|
5
5
|
addOptions() {
|
|
6
|
+
const parentOptions = this.parent?.() ?? {};
|
|
6
7
|
return {
|
|
7
|
-
...
|
|
8
|
+
...parentOptions,
|
|
8
9
|
sizes: ['inline', 'block', 'left', 'right']
|
|
9
10
|
};
|
|
10
11
|
},
|
|
11
12
|
parseHTML: () => [{ tag: 'img' }],
|
|
12
13
|
renderHTML({ HTMLAttributes }) {
|
|
13
|
-
const
|
|
14
|
+
const style = HTMLAttributes.style;
|
|
14
15
|
return [
|
|
15
16
|
'figure',
|
|
16
17
|
{ style },
|
package/dist/plugin/indent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { wrappingInputRule } from '@tiptap/core';
|
|
2
2
|
import OrderedListBase from '@tiptap/extension-ordered-list';
|
|
3
3
|
import toggleList from './toggleList';
|
|
4
|
-
import './korean.
|
|
4
|
+
import './korean.css';
|
|
5
5
|
export default OrderedListBase.extend({
|
|
6
6
|
priority: 20,
|
|
7
7
|
addAttributes() {
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import BuiltInTable from '@tiptap/extension-table';
|
|
1
|
+
import { Table as BuiltInTable } from '@tiptap/extension-table';
|
|
2
2
|
import { Plugin } from 'prosemirror-state';
|
|
3
3
|
import { tableEditing, columnResizing } from 'prosemirror-tables';
|
|
4
4
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
5
5
|
import deleteTable from './deleteTable';
|
|
6
|
-
import './style.
|
|
6
|
+
import './style.css';
|
|
7
|
+
const resolveTableElement = (view, pos) => {
|
|
8
|
+
if (!view)
|
|
9
|
+
return null;
|
|
10
|
+
const dom = view.nodeDOM(pos);
|
|
11
|
+
if (dom instanceof HTMLTableElement)
|
|
12
|
+
return dom;
|
|
13
|
+
if (!(dom instanceof HTMLElement))
|
|
14
|
+
return null;
|
|
15
|
+
const table = dom.querySelector('table');
|
|
16
|
+
return table instanceof HTMLTableElement ? table : null;
|
|
17
|
+
};
|
|
7
18
|
export default BuiltInTable.extend({
|
|
8
19
|
renderHTML() {
|
|
9
20
|
return [
|
|
@@ -17,7 +28,6 @@ export default BuiltInTable.extend({
|
|
|
17
28
|
];
|
|
18
29
|
},
|
|
19
30
|
addProseMirrorPlugins() {
|
|
20
|
-
const { isEditable } = this.editor;
|
|
21
31
|
return [
|
|
22
32
|
tableEditing(),
|
|
23
33
|
columnResizing({}),
|
|
@@ -26,25 +36,23 @@ export default BuiltInTable.extend({
|
|
|
26
36
|
decorations: (state) => {
|
|
27
37
|
const { doc } = state;
|
|
28
38
|
const decorations = [];
|
|
29
|
-
|
|
39
|
+
const isEditable = this.editor.isEditable;
|
|
40
|
+
const view = this.editor.view;
|
|
30
41
|
doc.descendants((node, pos) => {
|
|
31
42
|
if (node.type.name !== this.name)
|
|
32
43
|
return;
|
|
33
|
-
const
|
|
34
|
-
const table = elements[index];
|
|
44
|
+
const table = resolveTableElement(view, pos);
|
|
35
45
|
if (!table)
|
|
36
46
|
return;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const shadowRight = !!(element && element.scrollWidth > element.clientWidth);
|
|
47
|
+
table.classList.toggle('is-readonly', !isEditable);
|
|
48
|
+
const scrollable = table.parentElement;
|
|
49
|
+
const shadowRight = !!(scrollable instanceof HTMLElement && scrollable.scrollWidth > scrollable.clientWidth);
|
|
41
50
|
if (shadowRight)
|
|
42
51
|
decorations.push(Decoration.widget(pos + 1, () => {
|
|
43
52
|
const shadow = document.createElement('div');
|
|
44
53
|
shadow.className = `scrollable-shadow right ${isEditable ? 'is-editable' : ''}`;
|
|
45
54
|
return shadow;
|
|
46
55
|
}));
|
|
47
|
-
index++;
|
|
48
56
|
});
|
|
49
57
|
return DecorationSet.create(doc, decorations);
|
|
50
58
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.ProseMirror table {
|
|
2
|
-
td,
|
|
3
|
-
th {
|
|
2
|
+
& td,
|
|
3
|
+
& th {
|
|
4
4
|
position: relative;
|
|
5
5
|
min-width: 8px;
|
|
6
6
|
padding: 4px 8px;
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
border: 1px solid var(--primary-light3) !important;
|
|
10
10
|
transition: all 0.1s ease-in-out;
|
|
11
11
|
font-weight: normal;
|
|
12
|
+
}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
& td > *,
|
|
15
|
+
& th > * {
|
|
16
|
+
margin-bottom: 0;
|
|
16
17
|
}
|
|
17
18
|
}
|