@seorii/tiptap 0.3.0-next.9 → 0.4.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.
Files changed (55) hide show
  1. package/dist/i18n/index.d.ts +106 -6
  2. package/dist/i18n/index.js +56 -11
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +2 -1
  5. package/dist/plugin/command/emoji.d.ts +3 -17
  6. package/dist/plugin/command/emoji.js +51 -24
  7. package/dist/plugin/command/stores.svelte.d.ts +52 -0
  8. package/dist/plugin/command/stores.svelte.js +69 -0
  9. package/dist/plugin/command/suggest.d.ts +6 -19
  10. package/dist/plugin/command/suggest.js +149 -49
  11. package/dist/plugin/embed.d.ts +2 -2
  12. package/dist/plugin/embed.js +6 -2
  13. package/dist/plugin/iframe.js +1 -1
  14. package/dist/plugin/image/dragdrop.d.ts +2 -0
  15. package/dist/plugin/image/dragdrop.js +126 -15
  16. package/dist/plugin/image/index.js +4 -3
  17. package/dist/plugin/indent.js +0 -1
  18. package/dist/plugin/orderedlist/index.d.ts +1 -1
  19. package/dist/plugin/orderedlist/index.js +1 -1
  20. package/dist/plugin/orderedlist/{korean.scss → korean.css} +2 -2
  21. package/dist/plugin/resize/index.d.ts +8 -0
  22. package/dist/plugin/resize/index.js +454 -0
  23. package/dist/plugin/table/index.d.ts +1 -1
  24. package/dist/plugin/table/index.js +19 -11
  25. package/dist/plugin/table/style/{cell.scss → cell.css} +6 -5
  26. package/dist/plugin/table/style/{grip.scss → grip.css} +14 -19
  27. package/dist/plugin/table/style/resize.css +28 -0
  28. package/dist/plugin/table/style/{table.scss → table.css} +15 -17
  29. package/dist/plugin/table/style.css +4 -0
  30. package/dist/plugin/table/tableCell/index.js +2 -4
  31. package/dist/plugin/table/tableHeader/index.js +1 -2
  32. package/dist/plugin/upload/skeleton/UploadSkeleton.svelte +97 -0
  33. package/dist/plugin/upload/skeleton/UploadSkeleton.svelte.d.ts +5 -0
  34. package/dist/plugin/upload/skeleton/index.d.ts +30 -0
  35. package/dist/plugin/upload/skeleton/index.js +147 -0
  36. package/dist/plugin/youtube.js +1 -1
  37. package/dist/tiptap/Bubble.svelte +231 -92
  38. package/dist/tiptap/Bubble.svelte.d.ts +9 -6
  39. package/dist/tiptap/Command.svelte +160 -158
  40. package/dist/tiptap/Command.svelte.d.ts +2 -3
  41. package/dist/tiptap/Floating.svelte +51 -24
  42. package/dist/tiptap/Floating.svelte.d.ts +1 -0
  43. package/dist/tiptap/TipTap.svelte +302 -140
  44. package/dist/tiptap/TipTap.svelte.d.ts +10 -3
  45. package/dist/tiptap/ToolbarButton.svelte +30 -10
  46. package/dist/tiptap/ToolbarButton.svelte.d.ts +10 -6
  47. package/dist/tiptap/setMath.d.ts +2 -1
  48. package/dist/tiptap/setMath.js +74 -12
  49. package/dist/tiptap/tiptap.d.ts +9 -1
  50. package/dist/tiptap/tiptap.js +172 -16
  51. package/package.json +63 -57
  52. package/dist/plugin/command/stores.d.ts +0 -13
  53. package/dist/plugin/command/stores.js +0 -7
  54. package/dist/plugin/table/style/resize.scss +0 -26
  55. package/dist/plugin/table/style.scss +0 -4
@@ -1,18 +1,44 @@
1
- import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail, slashSelection } from './stores';
1
+ import { closeSlash, moveSlashSelection, runSlashItemAt, setSlashItems, setSlashLocation, setSlashProps, slashState } from './stores.svelte';
2
2
  import i18n from '../../i18n';
3
- import { fallbackUpload } from '../image/dragdrop';
4
- import { PluginKey } from 'prosemirror-state';
5
- import { Editor } from '@tiptap/core';
6
- import Suggestion from '@tiptap/suggestion';
7
- function fixRange(editor, range, split = '/') {
8
- const { state } = editor.view, { selection, doc } = state;
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 { insertUploadSkeleton } from '../upload/skeleton';
7
+ import { PluginKey, TextSelection } from '@tiptap/pm/state';
8
+ import Suggestion, {} from '@tiptap/suggestion';
9
+ const normalizeSearch = (value) => value.toLowerCase().trim();
10
+ const compactSearch = (value) => normalizeSearch(value).replace(/\s+/g, '');
11
+ function localeText(key, locale) {
12
+ const value = locale[key];
13
+ return typeof value === 'string' ? value : '';
14
+ }
15
+ function createKeywords(keys, extra = []) {
16
+ return [
17
+ ...new Set([...extra, ...keys.flatMap((key) => [localeText(key, enUs), localeText(key, koKr)])]
18
+ .map(normalizeSearch)
19
+ .filter(Boolean))
20
+ ];
21
+ }
22
+ function matchQuery(value, query, compactQuery) {
23
+ const normalizedValue = normalizeSearch(value);
24
+ return normalizedValue.includes(query) || compactSearch(normalizedValue).includes(compactQuery);
25
+ }
26
+ function matchItem(item, query, compactQuery) {
27
+ if (!query)
28
+ return true;
29
+ return [item.title, item.subtitle ?? '', ...(item.keywords ?? [])].some((value) => matchQuery(value, query, compactQuery));
30
+ }
31
+ function fixRange(editor, rawRange, split = '/') {
32
+ const range = { ...rawRange };
33
+ const { state } = editor.view;
34
+ const { selection, doc } = state;
9
35
  if (selection.$to.nodeBefore?.text?.includes?.(split)) {
10
36
  range.from = range.to;
11
37
  while (range.from > 0 && doc.textBetween(range.from - 1, range.from) !== split) {
12
38
  try {
13
39
  range.from -= 1;
14
40
  }
15
- catch (e) {
41
+ catch {
16
42
  range.from += 2;
17
43
  break;
18
44
  }
@@ -23,29 +49,35 @@ function fixRange(editor, range, split = '/') {
23
49
  try {
24
50
  range.to += 1;
25
51
  }
26
- catch (e) {
52
+ catch {
27
53
  range.to -= 1;
28
54
  break;
29
55
  }
30
56
  }
31
57
  return range;
32
58
  }
33
- export function getDetail(editor, range, opt) {
34
- slashSelection.set(() => editor.chain().focus().deleteRange(fixRange(editor, range)).run());
35
- slashDetail.set(opt);
59
+ export function getDetail(editor, range, option) {
60
+ slashState.selection = () => {
61
+ editor.chain().focus().deleteRange(fixRange(editor, range)).run();
62
+ };
63
+ slashState.detail = option;
36
64
  }
37
65
  export const suggest = {
38
66
  pluginKey: new PluginKey('slash-suggest'),
39
67
  char: '/',
40
68
  items: ({ query }) => {
69
+ const blocks = typeof window !== 'undefined'
70
+ ? (window.__tiptap_blocks ?? [])
71
+ : [];
41
72
  const raw = [
42
73
  {
43
74
  section: i18n('text'),
44
75
  list: [
45
76
  {
46
77
  icon: 'title',
47
- title: i18n('title') + ' 1',
78
+ title: `${i18n('title')} 1`,
48
79
  subtitle: i18n('title1Info'),
80
+ keywords: createKeywords(['title', 'title1Info'], ['heading 1', 'h1']),
49
81
  command: ({ editor, range }) => {
50
82
  editor
51
83
  .chain()
@@ -57,8 +89,9 @@ export const suggest = {
57
89
  },
58
90
  {
59
91
  icon: 'title',
60
- title: i18n('title') + ' 2',
92
+ title: `${i18n('title')} 2`,
61
93
  subtitle: i18n('title2Info'),
94
+ keywords: createKeywords(['title', 'title2Info'], ['heading 2', 'h2']),
62
95
  command: ({ editor, range }) => {
63
96
  editor
64
97
  .chain()
@@ -70,8 +103,9 @@ export const suggest = {
70
103
  },
71
104
  {
72
105
  icon: 'title',
73
- title: i18n('title') + ' 3',
106
+ title: `${i18n('title')} 3`,
74
107
  subtitle: i18n('title3Info'),
108
+ keywords: createKeywords(['title', 'title3Info'], ['heading 3', 'h3']),
75
109
  command: ({ editor, range }) => {
76
110
  editor
77
111
  .chain()
@@ -85,6 +119,7 @@ export const suggest = {
85
119
  icon: 'format_list_bulleted',
86
120
  title: i18n('unorderedList'),
87
121
  subtitle: i18n('unorderedListInfo'),
122
+ keywords: createKeywords(['unorderedList', 'unorderedListInfo'], ['bullet list', 'ul']),
88
123
  command: ({ editor, range }) => {
89
124
  editor.commands.deleteRange(fixRange(editor, range));
90
125
  editor.commands.toggleBulletList();
@@ -94,6 +129,7 @@ export const suggest = {
94
129
  icon: 'format_list_numbered',
95
130
  title: i18n('numberList'),
96
131
  subtitle: i18n('numberListInfo'),
132
+ keywords: createKeywords(['numberList', 'numberListInfo'], ['ordered list', 'ol']),
97
133
  command: ({ editor, range }) => {
98
134
  editor.commands.deleteRange(fixRange(editor, range));
99
135
  editor.commands.toggleOrderedList();
@@ -104,24 +140,43 @@ export const suggest = {
104
140
  {
105
141
  section: i18n('block'),
106
142
  list: [
107
- ...window.__tiptap_blocks,
143
+ ...blocks,
108
144
  {
109
145
  icon: 'image',
110
146
  title: i18n('image'),
111
147
  subtitle: i18n('imageInfo'),
148
+ keywords: createKeywords(['image', 'imageInfo']),
112
149
  command: ({ editor, range }) => {
113
150
  editor.chain().focus().deleteRange(fixRange(editor, range)).run();
114
151
  const input = document.createElement('input');
115
152
  input.type = 'file';
116
153
  input.accept = 'image/*';
117
154
  input.onchange = async () => {
118
- if (input.files) {
119
- const file = input.files[0];
120
- if (file) {
121
- const upload = window.__image_uploader || fallbackUpload;
122
- const src = await upload(file);
123
- editor.chain().focus().deleteRange(range).setImage({ src }).run();
155
+ if (!input.files?.length)
156
+ return;
157
+ const file = input.files[0];
158
+ if (!file)
159
+ return;
160
+ const skeleton = insertUploadSkeleton(editor, {
161
+ kind: 'image',
162
+ height: 220
163
+ });
164
+ try {
165
+ const upload = window.__image_uploader ?? fallbackUpload;
166
+ const src = await upload(file);
167
+ if (skeleton) {
168
+ skeleton.replaceWith({
169
+ type: 'image',
170
+ attrs: { src }
171
+ });
172
+ }
173
+ else {
174
+ editor.chain().focus().setImage({ src }).run();
124
175
  }
176
+ releaseObjectUrlOnImageSettled(editor.view, src);
177
+ }
178
+ catch {
179
+ skeleton?.remove();
125
180
  }
126
181
  };
127
182
  input.click();
@@ -131,24 +186,42 @@ export const suggest = {
131
186
  icon: 'code',
132
187
  title: i18n('codeBlock'),
133
188
  subtitle: i18n('codeBlockInfo'),
134
- command: ({ editor, range }) => getDetail(editor, range, {
135
- type: 'code',
136
- handler: (input) => {
137
- editor
138
- .chain()
139
- .focus()
140
- .deleteRange(fixRange(editor, range - 1))
141
- .setNode('codeBlock', { language: input })
142
- .run();
143
- }
144
- })
189
+ keywords: createKeywords(['codeBlock', 'codeBlockInfo'], ['code']),
190
+ command: ({ editor, range }) => {
191
+ editor
192
+ .chain()
193
+ .focus()
194
+ .deleteRange(fixRange(editor, range))
195
+ .setNode('codeBlock', { language: null })
196
+ .command(({ tr }) => {
197
+ const { from } = tr.selection;
198
+ const $from = tr.doc.resolve(from);
199
+ if ($from.parent.type.name === 'codeBlock') {
200
+ tr.setSelection(TextSelection.create(tr.doc, $from.start()));
201
+ return true;
202
+ }
203
+ const before = $from.nodeBefore;
204
+ if (before?.type.name === 'codeBlock') {
205
+ const positionInsideCodeBlock = from - before.nodeSize + 1;
206
+ tr.setSelection(TextSelection.create(tr.doc, positionInsideCodeBlock));
207
+ return true;
208
+ }
209
+ const after = $from.nodeAfter;
210
+ if (after?.type.name === 'codeBlock') {
211
+ tr.setSelection(TextSelection.create(tr.doc, from + 1));
212
+ }
213
+ return true;
214
+ })
215
+ .focus()
216
+ .run();
217
+ }
145
218
  },
146
219
  {
147
220
  icon: 'functions',
148
221
  title: i18n('mathBlock'),
149
222
  subtitle: i18n('mathBlockInfo'),
223
+ keywords: createKeywords(['mathBlock', 'mathBlockInfo'], ['latex', 'equation']),
150
224
  command: ({ editor, range }) => {
151
- const { to } = range;
152
225
  editor
153
226
  .chain()
154
227
  .focus()
@@ -162,6 +235,7 @@ export const suggest = {
162
235
  icon: 'table_chart',
163
236
  title: i18n('table'),
164
237
  subtitle: i18n('tableInfo'),
238
+ keywords: createKeywords(['table', 'tableInfo']),
165
239
  command: ({ editor, range }) => {
166
240
  editor
167
241
  .chain()
@@ -178,6 +252,7 @@ export const suggest = {
178
252
  icon: 'format_quote',
179
253
  title: i18n('blockquote'),
180
254
  subtitle: i18n('blockquoteInfo'),
255
+ keywords: createKeywords(['blockquote', 'blockquoteInfo'], ['quote']),
181
256
  command: ({ editor, range }) => {
182
257
  editor
183
258
  .chain()
@@ -192,6 +267,7 @@ export const suggest = {
192
267
  icon: 'iframe',
193
268
  title: i18n('iframe'),
194
269
  subtitle: i18n('iframeInfo'),
270
+ keywords: createKeywords(['iframe', 'iframeInfo'], ['embed', 'url']),
195
271
  command: ({ editor, range }) => getDetail(editor, range, {
196
272
  title: 'iframe',
197
273
  placeholder: 'url',
@@ -214,6 +290,7 @@ export const suggest = {
214
290
  icon: 'youtube_activity',
215
291
  title: i18n('youtube'),
216
292
  subtitle: i18n('youtubeInfo'),
293
+ keywords: createKeywords(['youtube', 'youtubeInfo'], ['video']),
217
294
  command: ({ editor, range }) => getDetail(editor, range, {
218
295
  title: 'youtube',
219
296
  placeholder: 'url',
@@ -230,38 +307,61 @@ export const suggest = {
230
307
  ]
231
308
  }
232
309
  ];
233
- const filtered = raw
310
+ const normalizedQuery = normalizeSearch(query);
311
+ const compactQuery = compactSearch(query);
312
+ return raw
234
313
  .map(({ section, list }) => ({
235
314
  section,
236
- list: list.filter((item) => item.title.toLowerCase().includes(query.toLowerCase()) ||
237
- item.subtitle.toLowerCase().includes(query.toLowerCase()))
315
+ list: list.filter((item) => matchItem(item, normalizedQuery, compactQuery))
238
316
  }))
239
317
  .filter(({ list }) => list.length > 0);
240
- return filtered;
241
318
  },
242
319
  render: () => {
243
320
  return {
244
321
  onStart: (props) => {
245
- let editor = props.editor;
246
- let range = props.range;
247
- let location = props.clientRect();
248
- slashProps.set({ editor, range });
249
- slashVisible.set(true);
250
- slashLocaltion.set({ x: location.x, y: location.y, height: location.height });
251
- slashItems.set(props.items);
252
- slashDetail.set(null);
322
+ const { editor, range } = props;
323
+ setSlashProps({ editor, range });
324
+ slashState.visible = true;
325
+ slashState.selectedIndex = 0;
326
+ setSlashItems(props.items);
327
+ slashState.detail = null;
328
+ const location = props.clientRect?.();
329
+ if (location) {
330
+ setSlashLocation({ x: location.x, y: location.y, height: location.height });
331
+ }
253
332
  },
254
333
  onUpdate(props) {
255
- slashItems.set(props.items);
334
+ setSlashProps({ editor: props.editor, range: props.range });
335
+ setSlashItems(props.items);
256
336
  },
257
337
  onKeyDown(props) {
338
+ if (props.event.key === 'ArrowUp') {
339
+ props.event.preventDefault();
340
+ moveSlashSelection(-1);
341
+ return true;
342
+ }
343
+ if (props.event.key === 'ArrowDown') {
344
+ props.event.preventDefault();
345
+ moveSlashSelection(1);
346
+ return true;
347
+ }
348
+ if (props.event.key === 'Tab') {
349
+ props.event.preventDefault();
350
+ moveSlashSelection(props.event.shiftKey ? -1 : 1);
351
+ return true;
352
+ }
353
+ if (props.event.key === 'Enter') {
354
+ props.event.preventDefault();
355
+ return runSlashItemAt(slashState.selectedIndex);
356
+ }
258
357
  if (props.event.key === 'Escape') {
259
- slashVisible.set(false);
358
+ closeSlash();
260
359
  return true;
261
360
  }
361
+ return false;
262
362
  },
263
363
  onExit() {
264
- slashVisible.set(false);
364
+ closeSlash();
265
365
  }
266
366
  };
267
367
  }
@@ -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: string;
14
+ width?: string;
15
+ height?: string;
16
16
  }) => ReturnType;
17
17
  };
18
18
  }
@@ -34,7 +34,7 @@ export default Node.create({
34
34
  renderHTML({ HTMLAttributes }) {
35
35
  return [
36
36
  'div',
37
- this.options.HTMLAttributes,
37
+ mergeAttributes(this.options.HTMLAttributes, { 'data-bubble-menu': 'false' }),
38
38
  ['embed', mergeAttributes(HTMLAttributes, { credentialless: true, crossorigin: 'anonymous' })]
39
39
  ];
40
40
  },
@@ -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(options);
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;
@@ -31,7 +31,7 @@ export default Node.create({
31
31
  renderHTML({ HTMLAttributes }) {
32
32
  return [
33
33
  'div',
34
- this.options.HTMLAttributes,
34
+ mergeAttributes(this.options.HTMLAttributes, { 'data-bubble-menu': 'false' }),
35
35
  [
36
36
  'iframe',
37
37
  mergeAttributes(HTMLAttributes, { credentialless: true, crossorigin: 'anonymous' })
@@ -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,67 @@
1
- import { Plugin, PluginKey } from 'prosemirror-state';
1
+ import { Plugin } from 'prosemirror-state';
2
+ import { insertUploadSkeleton } from '../upload/skeleton';
2
3
  export const fallbackUpload = async (image) => URL.createObjectURL(image);
4
+ const OBJECT_URL_PREFIX = 'blob:';
5
+ const OBJECT_URL_REVOKE_TIMEOUT_MS = 30_000;
6
+ const isObjectUrl = (src) => src.startsWith(OBJECT_URL_PREFIX);
7
+ const revokeObjectUrl = (src) => {
8
+ try {
9
+ URL.revokeObjectURL(src);
10
+ }
11
+ catch {
12
+ // no-op
13
+ }
14
+ };
15
+ export const releaseObjectUrlOnImageSettled = (view, src) => {
16
+ if (!isObjectUrl(src))
17
+ return;
18
+ let released = false;
19
+ const cleanups = [];
20
+ const release = () => {
21
+ if (released)
22
+ return;
23
+ released = true;
24
+ cleanups.forEach((cleanup) => cleanup());
25
+ cleanups.length = 0;
26
+ revokeObjectUrl(src);
27
+ };
28
+ const timer = setTimeout(release, OBJECT_URL_REVOKE_TIMEOUT_MS);
29
+ cleanups.push(() => clearTimeout(timer));
30
+ const bind = () => {
31
+ const images = Array.from(view.dom.querySelectorAll('img')).filter((image) => image.getAttribute('src') === src);
32
+ if (!images.length)
33
+ return false;
34
+ let pending = 0;
35
+ images.forEach((image) => {
36
+ if (image.complete)
37
+ return;
38
+ pending += 1;
39
+ const settle = () => {
40
+ image.removeEventListener('load', settle);
41
+ image.removeEventListener('error', settle);
42
+ pending -= 1;
43
+ if (pending <= 0)
44
+ release();
45
+ };
46
+ image.addEventListener('load', settle);
47
+ image.addEventListener('error', settle);
48
+ cleanups.push(() => {
49
+ image.removeEventListener('load', settle);
50
+ image.removeEventListener('error', settle);
51
+ });
52
+ });
53
+ if (pending <= 0)
54
+ release();
55
+ return true;
56
+ };
57
+ queueMicrotask(() => {
58
+ if (bind())
59
+ return;
60
+ requestAnimationFrame(() => {
61
+ bind();
62
+ });
63
+ });
64
+ };
3
65
  export const dropImagePlugin = () => {
4
66
  return new Plugin({
5
67
  props: {
@@ -12,13 +74,31 @@ export const dropImagePlugin = () => {
12
74
  const image = item.getAsFile();
13
75
  if (item.type.indexOf('image') === 0) {
14
76
  event.preventDefault();
77
+ const skeleton = insertUploadSkeleton({
78
+ state: view.state,
79
+ view
80
+ }, {
81
+ kind: 'image',
82
+ height: 220
83
+ });
15
84
  if (upload && image) {
16
- upload(image).then((src) => {
17
- const node = schema.nodes.image.create({
18
- src: src
19
- });
20
- const transaction = view.state.tr.replaceSelectionWith(node);
21
- view.dispatch(transaction);
85
+ upload(image)
86
+ .then((src) => {
87
+ if (skeleton) {
88
+ skeleton.replaceWith({
89
+ type: 'image',
90
+ attrs: { src }
91
+ });
92
+ }
93
+ else {
94
+ const node = schema.nodes.image.create({ src });
95
+ const transaction = view.state.tr.replaceSelectionWith(node);
96
+ view.dispatch(transaction);
97
+ }
98
+ releaseObjectUrlOnImageSettled(view, src);
99
+ })
100
+ .catch(() => {
101
+ skeleton?.remove();
22
102
  });
23
103
  }
24
104
  }
@@ -58,18 +138,49 @@ export const dropImagePlugin = () => {
58
138
  return false;
59
139
  images.forEach(async (image) => {
60
140
  const reader = new FileReader();
141
+ const skeleton = insertUploadSkeleton({
142
+ state: view.state,
143
+ view
144
+ }, {
145
+ kind: 'image',
146
+ height: 220,
147
+ at: coordinates.pos
148
+ });
61
149
  if (upload) {
62
- const node = schema.nodes.image.create({
63
- src: await upload(image)
64
- });
65
- const transaction = view.state.tr.insert(coordinates.pos, node);
66
- view.dispatch(transaction);
150
+ try {
151
+ const src = await upload(image);
152
+ if (skeleton) {
153
+ skeleton.replaceWith({
154
+ type: 'image',
155
+ attrs: { src }
156
+ });
157
+ }
158
+ else {
159
+ const node = schema.nodes.image.create({ src });
160
+ const transaction = view.state.tr.insert(coordinates.pos, node);
161
+ view.dispatch(transaction);
162
+ }
163
+ releaseObjectUrlOnImageSettled(view, src);
164
+ }
165
+ catch {
166
+ skeleton?.remove();
167
+ }
67
168
  }
68
169
  else {
69
170
  reader.onload = (readerEvent) => {
70
- const node = schema.nodes.image.create({
71
- src: readerEvent.target?.result
72
- });
171
+ const src = readerEvent.target?.result;
172
+ if (typeof src !== 'string') {
173
+ skeleton?.remove();
174
+ return;
175
+ }
176
+ if (skeleton) {
177
+ skeleton.replaceWith({
178
+ type: 'image',
179
+ attrs: { src }
180
+ });
181
+ return;
182
+ }
183
+ const node = schema.nodes.image.create({ src });
73
184
  const transaction = view.state.tr.insert(coordinates.pos, node);
74
185
  view.dispatch(transaction);
75
186
  };
@@ -3,17 +3,18 @@ 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
- ...this.parent?.(),
8
+ ...parentOptions,
8
9
  sizes: ['inline', 'block', 'left', 'right']
9
10
  };
10
11
  },
11
12
  parseHTML: () => [{ tag: 'img' }],
12
13
  renderHTML({ HTMLAttributes }) {
13
- const { style } = HTMLAttributes;
14
+ const style = HTMLAttributes.style;
14
15
  return [
15
16
  'figure',
16
- { style },
17
+ { style, 'data-bubble-menu': 'false' },
17
18
  ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
18
19
  ];
19
20
  },
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-param-reassign */
2
1
  // Sources:
3
2
  // https://github.com/ueberdosis/tiptap/issues/1036#issuecomment-981094752
4
3
  // https://github.com/django-tiptap/django_tiptap/blob/main/django_tiptap/templates/forms/tiptap_textarea.html#L453-L602
@@ -1,3 +1,3 @@
1
- import './korean.scss';
1
+ import './korean.css';
2
2
  declare const _default: import("@tiptap/core").Node<import("@tiptap/extension-ordered-list").OrderedListOptions, any>;
3
3
  export default _default;
@@ -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.scss';
4
+ import './korean.css';
5
5
  export default OrderedListBase.extend({
6
6
  priority: 20,
7
7
  addAttributes() {
@@ -11,11 +11,11 @@
11
11
  }
12
12
 
13
13
  .ProseMirror {
14
- ol[type='kors'] {
14
+ & ol[type='kors'] {
15
15
  list-style-type: kors;
16
16
  }
17
17
 
18
- ol[type='korc'] {
18
+ & ol[type='korc'] {
19
19
  list-style-type: korc;
20
20
  }
21
21
  }
@@ -0,0 +1,8 @@
1
+ import { Extension } from '@tiptap/core';
2
+ export type ResizeOptions = {
3
+ attributeTypes: string[];
4
+ showHandleAlways?: boolean;
5
+ showHandleOnActive?: boolean;
6
+ };
7
+ declare const _default: Extension<ResizeOptions, any>;
8
+ export default _default;