@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.
Files changed (45) hide show
  1. package/dist/i18n/index.d.ts +106 -6
  2. package/dist/i18n/index.js +56 -11
  3. package/dist/plugin/command/emoji.d.ts +3 -17
  4. package/dist/plugin/command/emoji.js +51 -24
  5. package/dist/plugin/command/stores.svelte.d.ts +52 -0
  6. package/dist/plugin/command/stores.svelte.js +69 -0
  7. package/dist/plugin/command/suggest.d.ts +6 -19
  8. package/dist/plugin/command/suggest.js +133 -51
  9. package/dist/plugin/embed.d.ts +2 -2
  10. package/dist/plugin/embed.js +5 -1
  11. package/dist/plugin/image/dragdrop.d.ts +2 -0
  12. package/dist/plugin/image/dragdrop.js +66 -2
  13. package/dist/plugin/image/index.js +3 -2
  14. package/dist/plugin/indent.js +0 -1
  15. package/dist/plugin/orderedlist/index.d.ts +1 -1
  16. package/dist/plugin/orderedlist/index.js +1 -1
  17. package/dist/plugin/orderedlist/{korean.scss → korean.css} +2 -2
  18. package/dist/plugin/table/index.d.ts +1 -1
  19. package/dist/plugin/table/index.js +19 -11
  20. package/dist/plugin/table/style/{cell.scss → cell.css} +6 -5
  21. package/dist/plugin/table/style/{grip.scss → grip.css} +14 -19
  22. package/dist/plugin/table/style/resize.css +28 -0
  23. package/dist/plugin/table/style/{table.scss → table.css} +15 -17
  24. package/dist/plugin/table/style.css +4 -0
  25. package/dist/plugin/table/tableCell/index.js +2 -4
  26. package/dist/plugin/table/tableHeader/index.js +1 -2
  27. package/dist/tiptap/Bubble.svelte +106 -71
  28. package/dist/tiptap/Bubble.svelte.d.ts +8 -6
  29. package/dist/tiptap/Command.svelte +160 -158
  30. package/dist/tiptap/Command.svelte.d.ts +2 -3
  31. package/dist/tiptap/Floating.svelte +51 -24
  32. package/dist/tiptap/Floating.svelte.d.ts +1 -0
  33. package/dist/tiptap/TipTap.svelte +215 -135
  34. package/dist/tiptap/TipTap.svelte.d.ts +7 -3
  35. package/dist/tiptap/ToolbarButton.svelte +30 -10
  36. package/dist/tiptap/ToolbarButton.svelte.d.ts +10 -6
  37. package/dist/tiptap/setMath.d.ts +2 -1
  38. package/dist/tiptap/setMath.js +74 -12
  39. package/dist/tiptap/tiptap.d.ts +9 -1
  40. package/dist/tiptap/tiptap.js +170 -16
  41. package/package.json +63 -57
  42. package/dist/plugin/command/stores.d.ts +0 -13
  43. package/dist/plugin/command/stores.js +0 -7
  44. package/dist/plugin/table/style/resize.scss +0 -26
  45. package/dist/plugin/table/style.scss +0 -4
@@ -1,18 +1,43 @@
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 { 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 (e) {
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 (e) {
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, opt) {
34
- slashSelection.set(() => editor.chain().focus().deleteRange(fixRange(editor, range)).run());
35
- slashDetail.set(opt);
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') + ' 1',
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') + ' 2',
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') + ' 3',
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
- ...window.__tiptap_blocks,
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
- 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();
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
- 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
- })
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 filtered = raw
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.title.toLowerCase().includes(query.toLowerCase()) ||
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
- 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);
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
- slashItems.set(props.items);
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
- slashVisible.set(false);
340
+ closeSlash();
260
341
  return true;
261
342
  }
343
+ return false;
262
344
  },
263
345
  onExit() {
264
- slashVisible.set(false);
346
+ closeSlash();
265
347
  }
266
348
  };
267
349
  }
@@ -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
  }
@@ -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;
@@ -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, PluginKey } from 'prosemirror-state';
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: await upload(image)
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
- ...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
17
  { style },
@@ -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
  }
@@ -1,3 +1,3 @@
1
- import './style.scss';
1
+ import './style.css';
2
2
  declare const _default: import("@tiptap/core").Node<import("@tiptap/extension-table").TableOptions, any>;
3
3
  export default _default;
@@ -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.scss';
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
- let index = 0;
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 elements = document.getElementsByClassName('as-table');
34
- const table = elements[index];
44
+ const table = resolveTableElement(view, pos);
35
45
  if (!table)
36
46
  return;
37
- if (!isEditable)
38
- table.classList.add('is-readonly');
39
- const element = table.parentElement;
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
- margin-bottom: 0;
15
- }
14
+ & td > *,
15
+ & th > * {
16
+ margin-bottom: 0;
16
17
  }
17
18
  }