@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,23 +1,45 @@
1
1
  <script lang="ts">
2
2
  import { BubbleMenu } from 'svelte-tiptap';
3
- import { getContext, tick } from 'svelte';
3
+ import { flushSync, getContext, tick, untrack } from 'svelte';
4
4
  import 'tippy.js/animations/shift-away-subtle.css';
5
5
  import ToolbarButton from './ToolbarButton.svelte';
6
6
  import { isTableAnySelected } from '../plugin/table/util';
7
7
  import deleteTable from '../plugin/table/deleteTable';
8
8
  import setMath from './setMath';
9
9
  import { Button, Icon, IconButton, Input, List, OneLine, Paper, Render } from 'nunui';
10
- import i18n from '../i18n';
10
+ import defaultI18n, { I18N_CONTEXT, type I18nTranslate } from '../i18n';
11
11
  import ColorPicker from 'svelte-awesome-color-picker';
12
12
  import { isTextSelection } from '@tiptap/core';
13
+ import { NodeSelection, type EditorState, type Selection } from '@tiptap/pm/state';
14
+ import type { EditorView } from '@tiptap/pm/view';
13
15
 
14
- let { colors = [], editable, override, children } = $props();
16
+ type Props = {
17
+ colors?: string[];
18
+ editable?: boolean;
19
+ override?: any;
20
+ docked?: boolean;
21
+ children?: any;
22
+ };
23
+
24
+ let { colors = [], editable, override, docked = false, children }: Props = $props();
15
25
 
16
26
  const editor = getContext<{ v: any; c: number }>('editor');
27
+ const i18nFromContext = getContext<I18nTranslate | undefined>(I18N_CONTEXT);
28
+ const i18n: I18nTranslate = (...args) =>
29
+ i18nFromContext ? i18nFromContext(...args) : defaultI18n(...args);
17
30
  const tiptap = $derived(editor.v);
31
+ const currentTextColor = $derived.by(() => {
32
+ editor.c;
33
+ const color = tiptap?.getAttributes('textStyle')?.color;
34
+ return typeof color === 'string' ? color.trim().toLowerCase() : '';
35
+ });
36
+ const hasTextColor = $derived.by(() => {
37
+ editor.c;
38
+ return !!tiptap?.getAttributes('textStyle')?.color;
39
+ });
18
40
 
19
- let selection = $state(null);
20
- let table = $state(false);
41
+ let selection = $state<Selection | null>(null);
42
+ let table = $state<number[] | false>(false);
21
43
  let sel = $state('');
22
44
  let _sel = $state('');
23
45
  let link = $state(false);
@@ -25,6 +47,7 @@
25
47
 
26
48
  $effect(() => {
27
49
  let _ = editor.c;
50
+ flushSync();
28
51
  selection = tiptap?.state?.selection;
29
52
  });
30
53
 
@@ -45,19 +68,46 @@
45
68
  });
46
69
 
47
70
  $effect(() => {
48
- if (tiptap && link) {
49
- if (href) {
50
- tiptap.chain().setLink({ href }).run();
51
- } else if (tiptap.getAttributes('link').href) {
52
- tiptap.chain().unsetLink().run();
71
+ const _href = href;
72
+ untrack(() => {
73
+ if (tiptap && link) {
74
+ if (href) {
75
+ tiptap.chain().setLink({ href: _href }).run();
76
+ } else if (tiptap.getAttributes('link').href) {
77
+ tiptap.chain().unsetLink().run();
78
+ }
53
79
  }
54
- }
80
+ });
55
81
  });
56
82
 
57
- const shouldShow = ({ view, state, from, to }) => {
83
+ const shouldShow = ({
84
+ state,
85
+ view,
86
+ from,
87
+ to
88
+ }: {
89
+ view: EditorView;
90
+ state: EditorState;
91
+ from: number;
92
+ to: number;
93
+ }) => {
94
+ if (!tiptap?.isEditable) return false;
95
+
58
96
  const { doc, selection } = state;
59
97
  const { empty } = selection;
60
98
 
99
+ if (selection instanceof NodeSelection && selection.node.isBlock) {
100
+ const nodeDom = view.nodeDOM(from);
101
+ if (
102
+ nodeDom instanceof Element &&
103
+ (nodeDom.hasAttribute('data-hide-bubble-menu') ||
104
+ nodeDom.getAttribute('data-bubble-menu') === 'false' ||
105
+ Boolean(nodeDom.querySelector('[data-hide-bubble-menu], [data-bubble-menu="false"]')))
106
+ ) {
107
+ return false;
108
+ }
109
+ }
110
+
61
111
  const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
62
112
 
63
113
  return !(empty || isEmptyTextBlock);
@@ -65,32 +115,23 @@
65
115
  </script>
66
116
 
67
117
  {#if tiptap}
68
- <BubbleMenu
69
- editor={tiptap}
70
- updateDelay={50}
71
- {shouldShow}
72
- tippyOptions={{
73
- moveTransition: 'transform 0.2s cubic-bezier(1,.5,0,.85)',
74
- animation: 'shift-away-subtle',
75
- duration: [200, 50]
76
- }}
77
- >
118
+ {#snippet toolbar()}
78
119
  {#if override}
79
- <main>
120
+ <main class:docked>
80
121
  <Render it={override} />
81
122
  </main>
82
123
  {:else}
83
- <main>
124
+ <main class:docked>
84
125
  {#if link}
85
126
  <div class="link">
86
127
  <p>
87
128
  <Icon icon="link" />
88
129
  {i18n('link')}
89
130
  </p>
90
- <Input placeholder="url" fullWidth bind:value={href} autofocus />
131
+ <Input placeholder="url" bind:value={href} autofocus />
91
132
  <div>
92
133
  <Button
93
- tabindex="0"
134
+ tabindex={0}
94
135
  transparent
95
136
  small
96
137
  onclick={() => {
@@ -100,7 +141,7 @@
100
141
  }}
101
142
  >{i18n('delete')}
102
143
  </Button>
103
- <Button tabindex="0" transparent small onclick={() => (link = false)}
144
+ <Button tabindex={0} transparent small onclick={() => (link = false)}
104
145
  >{i18n('close')}</Button
105
146
  >
106
147
  </div>
@@ -130,11 +171,11 @@
130
171
  <ToolbarButton icon="close" handler={() => deleteTable({ editor: tiptap })} />
131
172
  {:else}
132
173
  {#if editable}
133
- <Paper hover bl>
174
+ <Paper hover bl remap>
134
175
  {#snippet target()}
135
176
  <IconButton size="1.2em" icon="format_align_left" />
136
177
  {/snippet}
137
- <div style="margin: -6px;font-size: 0.6em">
178
+ <div class="menu-list align-menu">
138
179
  <List>
139
180
  <OneLine
140
181
  icon="format_align_left"
@@ -172,15 +213,21 @@
172
213
  <ToolbarButton icon="subscript" prop="subscript" />
173
214
  <ToolbarButton icon="border_color" prop="highlight" />
174
215
  {#if editable}
175
- <ToolbarButton icon="functions" handler={() => setMath(tiptap)} />
216
+ <ToolbarButton icon="functions" prop="math_inline" handler={() => setMath(tiptap)} />
176
217
  {/if}
177
- <Paper bl block>
218
+ <Paper bl remap>
178
219
  {#snippet target()}
179
220
  <IconButton size="1.2em" icon="palette" />
180
221
  {/snippet}
181
- <div style="font-size: 0.6em">
222
+ <div class="menu-list">
182
223
  <div class="colors">
183
- <Button small outlined onclick={() => tiptap.chain().focus().unsetColor().run()}>
224
+ <Button
225
+ small
226
+ outlined
227
+ active={!hasTextColor}
228
+ class={!hasTextColor ? 'color-active' : undefined}
229
+ onclick={() => tiptap.chain().focus().unsetColor().run()}
230
+ >
184
231
  {i18n('default')}
185
232
  </Button>
186
233
  <Paper bl remap block>
@@ -189,12 +236,14 @@
189
236
  small
190
237
  full
191
238
  outlined
239
+ active={hasTextColor}
240
+ class={hasTextColor ? 'color-active' : undefined}
192
241
  onclick={() => tiptap.chain().focus().unsetColor().run()}
193
242
  >
194
- <Icon colorize />
243
+ <Icon icon="palette" />
195
244
  </Button>
196
245
  {/snippet}
197
- <div onclick={(event) => event.stopPropagation()} onmousedown={(event) => event.stopPropagation()}>
246
+ <div role="presentation" onmousedown={(event) => event.stopPropagation()}>
198
247
  <ColorPicker
199
248
  isDialog={false}
200
249
  onInput={(event) => {
@@ -203,16 +252,15 @@
203
252
  />
204
253
  </div>
205
254
  </Paper>
206
- {#each colors as color}
255
+ {#each colors as color (color)}
207
256
  <Button
208
257
  small
209
258
  outlined
259
+ active={currentTextColor === color.toLowerCase()}
260
+ class={currentTextColor === color.toLowerCase() ? 'color-active' : undefined}
210
261
  onclick={() => tiptap.chain().focus().setColor(color).run()}
211
262
  >
212
- <span
213
- style:background={color}
214
- class="pal"
215
- ></span>
263
+ <span style:background={color} class="pal"></span>
216
264
  </Button>
217
265
  {/each}
218
266
  </div>
@@ -226,57 +274,148 @@
226
274
  <Render it={children} />
227
275
  </main>
228
276
  {/if}
229
- </BubbleMenu>
277
+ {/snippet}
278
+
279
+ {#if docked && tiptap.isEditable}
280
+ <div class="docked-menu">
281
+ {@render toolbar()}
282
+ </div>
283
+ {:else}
284
+ <BubbleMenu
285
+ editor={tiptap}
286
+ updateDelay={50}
287
+ {shouldShow}
288
+ tippyOptions={{
289
+ moveTransition: 'transform 0.2s cubic-bezier(1,.5,0,.85)',
290
+ animation: 'shift-away-subtle',
291
+ duration: [200, 50]
292
+ }}
293
+ >
294
+ {@render toolbar()}
295
+ </BubbleMenu>
296
+ {/if}
230
297
  {/if}
231
298
 
232
- <style>main {
233
- box-shadow: var(--shadow);
234
- background: var(--surface, #fff);
235
- color: var(--on-surface, #000);
236
- padding: 8px;
237
- border-radius: 4px;
238
- display: flex;
239
- align-items: center;
240
- justify-content: center;
241
- font-size: 1.2em;
242
- }
243
- main > :global(*) {
244
- margin: 0 2px;
245
- }
246
- main > :global(*):first-child {
247
- margin-left: 0;
248
- }
249
- main > :global(*):last-child {
250
- margin-right: 0;
251
- }
252
-
253
- .link {
254
- display: flex;
255
- flex-direction: column;
256
- font-size: 0.7em;
257
- }
258
- .link p {
259
- margin: 0 0 0.6em 0;
260
- }
261
- .link div {
262
- margin-top: 0.6em;
263
- display: flex;
264
- justify-content: flex-end;
265
- }
266
-
267
- .colors {
268
- display: grid;
269
- grid-template-columns: 1fr 1fr 1fr;
270
- gap: 4px;
271
- }
272
- .colors > :global(:first-child) {
273
- grid-column: 1/3;
274
- }
275
-
276
- .pal {
277
- width: 20px;
278
- height: 16px;
279
- border-radius: 4px;
280
- display: inline-block;
281
- margin-bottom: -2px;
282
- }</style>
299
+ <style>
300
+ main {
301
+ box-shadow:
302
+ 0 10px 24px rgba(22, 37, 63, 0.14),
303
+ 0 2px 8px rgba(22, 37, 63, 0.1);
304
+ background: var(--surface, #fff);
305
+ border: 1px solid var(--primary-light2, #d5dff3);
306
+ color: var(--on-surface, #000);
307
+ padding: 8px 10px;
308
+ border-radius: 12px;
309
+ display: flex;
310
+ flex-wrap: nowrap;
311
+ gap: 4px;
312
+ align-items: center;
313
+ justify-content: center;
314
+ font-size: 1.2em;
315
+ & > :global(*) {
316
+ margin: 0;
317
+ }
318
+ }
319
+
320
+ .docked-menu {
321
+ position: sticky;
322
+ top: var(--tiptap-toolbar-sticky-top, 8px);
323
+ z-index: 40;
324
+ display: flex;
325
+ justify-content: center;
326
+ margin-bottom: 10px;
327
+ padding: 4px 0;
328
+ }
329
+
330
+ main.docked {
331
+ width: 100%;
332
+ justify-content: flex-start;
333
+ border-radius: 14px;
334
+ overflow-x: auto;
335
+ overflow-y: hidden;
336
+ backdrop-filter: blur(10px) saturate(1.15);
337
+ -webkit-backdrop-filter: blur(10px) saturate(1.15);
338
+ }
339
+
340
+ main:not(.docked) {
341
+ overflow-x: auto;
342
+ overflow-y: hidden;
343
+ }
344
+
345
+ main.docked > :global(*) {
346
+ flex-shrink: 0;
347
+ }
348
+
349
+ main.docked :global(button) {
350
+ font-size: 1.08em;
351
+ }
352
+
353
+ main.docked > :global(main.i) {
354
+ display: flex;
355
+ align-items: center;
356
+ line-height: 1;
357
+ transform: translateY(-1px);
358
+ }
359
+
360
+ .menu-list {
361
+ font-size: 0.6em;
362
+ }
363
+
364
+ .align-menu {
365
+ margin: -6px;
366
+ }
367
+
368
+ main.docked .menu-list {
369
+ font-size: 0.72em;
370
+ }
371
+
372
+ @media (max-width: 768px) {
373
+ main {
374
+ font-size: 1.05em;
375
+ }
376
+ }
377
+
378
+ .link {
379
+ display: flex;
380
+ flex-direction: column;
381
+ font-size: 0.7em;
382
+
383
+ & p {
384
+ margin: 0 0 0.6em 0;
385
+ }
386
+
387
+ & div {
388
+ margin-top: 0.6em;
389
+ display: flex;
390
+ justify-content: flex-end;
391
+ }
392
+ }
393
+
394
+ .colors {
395
+ display: grid;
396
+ grid-template-columns: 1fr 1fr 1fr;
397
+ gap: 4px;
398
+
399
+ & > :global(:first-child) {
400
+ grid-column: 1/3;
401
+ }
402
+
403
+ & :global(button.color-active) {
404
+ box-shadow: inset 0 0 0 1px var(--primary-dark2, #2d4b8f);
405
+ background: color-mix(in srgb, var(--primary-light1, #eef3ff) 65%, var(--surface, #fff));
406
+ }
407
+
408
+ & :global(button.color-active .pal) {
409
+ outline: 1px solid color-mix(in srgb, var(--on-surface, #111) 65%, transparent);
410
+ outline-offset: 1px;
411
+ }
412
+ }
413
+
414
+ .pal {
415
+ width: 20px;
416
+ height: 16px;
417
+ border-radius: 4px;
418
+ display: inline-block;
419
+ margin-bottom: -2px;
420
+ }
421
+ </style>
@@ -1,8 +1,11 @@
1
1
  import 'tippy.js/animations/shift-away-subtle.css';
2
- declare const Bubble: import("svelte").Component<{
3
- colors?: unknown;
4
- editable: unknown;
5
- override: unknown;
6
- children: unknown;
7
- }, {}, "">;
2
+ type Props = {
3
+ colors?: string[];
4
+ editable?: boolean;
5
+ override?: any;
6
+ docked?: boolean;
7
+ children?: any;
8
+ };
9
+ declare const Bubble: import("svelte").Component<Props, {}, "">;
10
+ type Bubble = ReturnType<typeof Bubble>;
8
11
  export default Bubble;