@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,131 +1,128 @@
1
1
  <script lang="ts">
2
2
  import { Button, IconButton, Input, List, OneLine, TwoLine } from 'nunui';
3
- import { getContext } from 'svelte';
3
+ import { getContext, tick } from 'svelte';
4
4
  import {
5
- slashVisible,
6
- slashItems,
7
- slashLocaltion,
8
- slashProps,
9
- slashDetail,
10
- slashSelection
11
- } from '../plugin/command/stores';
5
+ isSlashGroup,
6
+ isSlashItem,
7
+ slashState,
8
+ type SlashGroup,
9
+ type SlashItem
10
+ } from '../plugin/command/stores.svelte';
12
11
  import { fly, slide } from 'svelte/transition';
13
12
  import { quartOut } from 'svelte/easing';
14
- import i18n from '../i18n';
13
+ import defaultI18n, { I18N_CONTEXT, type I18nTranslate } from '../i18n';
15
14
 
16
15
  const editor = getContext<{ v: any }>('editor');
16
+ const i18nFromContext = getContext<I18nTranslate | undefined>(I18N_CONTEXT);
17
+ const i18n: I18nTranslate = (...args) =>
18
+ i18nFromContext ? i18nFromContext(...args) : defaultI18n(...args);
17
19
  const tiptap = $derived(editor.v);
18
- let { selectedIndex = 0 } = $props();
19
20
 
20
- let height = $state(0),
21
- elements = $state([]);
21
+ let height = $state(0);
22
22
  let input = $state(''),
23
- focus: any = $state(null);
23
+ focus = $state<HTMLInputElement | HTMLTextAreaElement | undefined>(undefined);
24
+ let menu = $state<HTMLElement | null>(null);
25
+
26
+ const emojiItems = $derived(slashState.items.filter(isSlashItem));
27
+ const groupedItems = $derived(slashState.items.filter(isSlashGroup));
28
+
29
+ function runCommand(item: SlashItem | undefined) {
30
+ if (!item) return;
31
+ const { editor, range } = slashState.props;
32
+ if (!editor || !range) return;
33
+
34
+ item.command({ editor, range });
35
+ setTimeout(() => tiptap?.commands?.focus?.());
36
+ }
37
+
38
+ function runDetailCommand() {
39
+ const detail = slashState.detail;
40
+ if (!detail || detail === 'emoji') return;
41
+
42
+ slashState.selection?.();
43
+ detail.handler(input);
44
+ }
24
45
 
25
46
  $effect(() => {
26
- if ($slashVisible) input = '';
47
+ if (slashState.visible) input = '';
27
48
  });
28
49
  $effect(() => {
29
50
  focus;
30
51
  setTimeout(() => focus?.focus?.(), 100);
31
52
  });
53
+
54
+ $effect(() => {
55
+ if (!slashState.visible) return;
56
+ const detail = slashState.detail;
57
+ if (detail && detail !== 'emoji') return;
58
+
59
+ const selectedIndex = slashState.selectedIndex;
60
+ void tick().then(() => {
61
+ const target = menu?.querySelector<HTMLElement>(`[data-slash-index="${selectedIndex}"]`);
62
+ target?.scrollIntoView({ block: 'nearest' });
63
+ });
64
+ });
32
65
  </script>
33
66
 
34
67
  <svelte:window bind:innerHeight={height} />
35
68
 
36
- {#if $slashVisible}
37
- <div class="scrim" onclick={() => ($slashVisible = false)} />
69
+ {#if slashState.visible}
70
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
71
+ <div class="scrim" onclick={() => (slashState.visible = false)}></div>
38
72
  <main
39
- style="left: {$slashLocaltion.x}px; top: {$slashLocaltion.y + $slashLocaltion.height + 384 >
73
+ bind:this={menu}
74
+ style="left: {slashState.location.x}px; top: {slashState.location.y +
75
+ slashState.location.height +
76
+ 384 >
40
77
  height
41
- ? $slashLocaltion.y - $slashLocaltion.height - 384
42
- : $slashLocaltion.y + $slashLocaltion.height}px;"
78
+ ? slashState.location.y - slashState.location.height - 384
79
+ : slashState.location.y + slashState.location.height}px;"
43
80
  transition:fly={{ y: 10, duration: 200, easing: quartOut }}
44
81
  >
45
- {#if $slashDetail === 'emoji'}
82
+ {#if slashState.detail === 'emoji'}
46
83
  <div class="list">
47
84
  <List>
48
- {#each $slashItems as { title, command }, i (title)}
49
- <div transition:slide={{ duration: 400, easing: quartOut }}>
85
+ {#each emojiItems as item, i (item.title)}
86
+ <div data-slash-index={i} transition:slide={{ duration: 400, easing: quartOut }}>
50
87
  <OneLine
51
- onclick={() => {
52
- command?.($slashProps);
53
- setTimeout(() => tiptap.commands.focus());
54
- }}
55
- bind:this={elements[i]}
56
- {title}
57
- active={selectedIndex === i}
88
+ onclick={() => runCommand(item)}
89
+ title={item.title}
90
+ active={slashState.selectedIndex === i}
58
91
  />
59
92
  </div>
60
93
  {/each}
61
- {#if !$slashItems.length}
94
+ {#if !emojiItems.length}
62
95
  <div class="section" transition:slide={{ duration: 400, easing: quartOut }}>
63
96
  {i18n('noResult')}
64
97
  </div>
65
98
  {/if}
66
99
  </List>
67
100
  </div>
68
- {:else if $slashDetail?.type === 'code'}
101
+ {:else if slashState.detail}
69
102
  <div class="detail">
70
103
  <header>
71
- <IconButton icon="arrow_back" onclick={() => ($slashDetail = null)} />
72
- <div class="title">{i18n('insertCode')}</div>
73
- </header>
74
- <div>
75
- <Button
76
- small
77
- onclick={() => {
78
- $slashSelection?.();
79
- $slashDetail.handler(undefined);
80
- }}>{i18n('auto')}</Button
81
- >
82
- {#each ['cpp', 'python', 'java'] as lang}
83
- <Button
84
- small
85
- outlined
86
- onclick={() => {
87
- $slashSelection?.();
88
- $slashDetail.handler(lang);
89
- }}>{lang}</Button
90
- >
91
- {/each}
92
- </div>
93
- </div>
94
- {:else if $slashDetail}
95
- <div class="detail">
96
- <header>
97
- <IconButton icon="arrow_back" onclick={() => ($slashDetail = null)} />
98
- <div class="title">{$slashDetail.title}</div>
104
+ <IconButton icon="arrow_back" onclick={() => (slashState.detail = null)} />
105
+ <div class="title">{slashState.detail.title}</div>
99
106
  </header>
100
107
  <Input
101
- placeholder={$slashDetail.placeholder}
102
- fullWidth
108
+ placeholder={slashState.detail.placeholder}
109
+ block
103
110
  bind:value={input}
104
111
  bind:input={focus}
105
- onsubmit={() => {
106
- $slashSelection?.();
107
- $slashDetail.handler(input);
108
- }}
112
+ onsubmit={runDetailCommand}
109
113
  />
110
114
  <footer>
111
115
  <Button
112
- tabindex="0"
116
+ tabindex={0}
113
117
  transparent
114
118
  small
115
119
  onclick={() => {
116
120
  input = '';
117
- $slashDetail = null;
121
+ slashState.detail = null;
118
122
  }}
119
123
  >{i18n('cancel')}
120
124
  </Button>
121
- <Button
122
- tabindex="0"
123
- transparent
124
- small
125
- onclick={() => {
126
- $slashSelection?.();
127
- $slashDetail.handler(input);
128
- }}
125
+ <Button tabindex={0} transparent small onclick={runDetailCommand}
129
126
  >{i18n('insert')}
130
127
  </Button>
131
128
  </footer>
@@ -133,33 +130,32 @@
133
130
  {:else}
134
131
  <div class="list">
135
132
  <List>
136
- {#each $slashItems as { section, list }, j (section)}
137
- {@const lastCount = $slashItems
133
+ {#each groupedItems as group, j (group.section)}
134
+ {@const lastCount = groupedItems
138
135
  .slice(0, j)
139
136
  .reduce((acc, cur) => acc + cur.list.length, 0)}
140
137
  <div class="section" transition:slide={{ duration: 400, easing: quartOut }}>
141
- {section}
138
+ {group.section}
142
139
  </div>
143
140
  <div transition:slide={{ duration: 400, easing: quartOut }}>
144
- {#each list || [] as { title, subtitle, icon, command, section }, i (title)}
145
- <div transition:slide={{ duration: 400, easing: quartOut }}>
141
+ {#each group.list as item, i (item.title)}
142
+ <div
143
+ data-slash-index={i + lastCount}
144
+ transition:slide={{ duration: 400, easing: quartOut }}
145
+ >
146
146
  <TwoLine
147
- onmouseenter={() => (selectedIndex = i + lastCount)}
148
- onclick={() => {
149
- command?.($slashProps);
150
- setTimeout(() => tiptap.commands.focus());
151
- }}
152
- bind:this={elements[i + lastCount]}
153
- {icon}
154
- {title}
155
- subtitle={subtitle || ''}
156
- active={selectedIndex === i + lastCount}
147
+ onmouseenter={() => (slashState.selectedIndex = i + lastCount)}
148
+ onclick={() => runCommand(item)}
149
+ icon={item.icon}
150
+ title={item.title}
151
+ subtitle={item.subtitle || ''}
152
+ active={slashState.selectedIndex === i + lastCount}
157
153
  />
158
154
  </div>
159
155
  {/each}
160
156
  </div>
161
157
  {/each}
162
- {#if !$slashItems.length}
158
+ {#if !groupedItems.length}
163
159
  <div class="section" transition:slide={{ duration: 400, easing: quartOut }}>
164
160
  {i18n('noResult')}
165
161
  </div>
@@ -170,70 +166,76 @@
170
166
  </main>
171
167
  {/if}
172
168
 
173
- <style>.scrim {
174
- position: fixed;
175
- top: 0;
176
- left: 0;
177
- width: 100%;
178
- height: 100%;
179
- cursor: default;
180
- z-index: 0;
181
- }
182
-
183
- main {
184
- position: fixed;
185
- background: var(--surface, #fff);
186
- width: 220px;
187
- max-height: 384px;
188
- border-radius: 4px;
189
- overflow-y: scroll;
190
- z-index: 10;
191
- box-shadow: var(--shadow);
192
- }
193
-
194
- .section {
195
- padding: 8px 16px;
196
- font-size: 0.8em;
197
- font-weight: 300;
198
- color: var(--on-surface, #000);
199
- opacity: 0.7;
200
- }
201
-
202
- .list {
203
- color: var(--primary-dark7);
204
- }
205
- .list :global .title {
206
- font-size: 0.7em !important;
207
- font-weight: 300 !important;
208
- margin-bottom: 4px;
209
- }
210
- .list :global .subtitle {
211
- font-size: 0.8em !important;
212
- font-weight: 300 !important;
213
- color: var(--primary-dark1);
214
- }
215
-
216
- .detail {
217
- font-size: 0.8em;
218
- padding: 8px;
219
- display: flex;
220
- flex-direction: column;
221
- }
222
-
223
- header {
224
- display: flex;
225
- align-items: center;
226
- margin-bottom: 6px;
227
- }
228
- header > :global(*) {
229
- margin-right: 8px;
230
- }
231
- header > :global(*):last-child {
232
- margin-right: 0;
233
- }
234
-
235
- footer {
236
- margin-top: 0.6em;
237
- display: flex;
238
- justify-content: flex-end;
239
- }</style>
169
+ <style>
170
+ .scrim {
171
+ position: fixed;
172
+ top: 0;
173
+ left: 0;
174
+ width: 100%;
175
+ height: 100%;
176
+ cursor: default;
177
+ z-index: 0;
178
+ }
179
+
180
+ main {
181
+ position: fixed;
182
+ background: var(--surface, #fff);
183
+ width: 220px;
184
+ max-height: 384px;
185
+ border-radius: 4px;
186
+ overflow-y: scroll;
187
+ z-index: 10;
188
+ box-shadow: var(--shadow);
189
+ }
190
+
191
+ .section {
192
+ padding: 8px 16px;
193
+ font-size: 0.8em;
194
+ font-weight: 300;
195
+ color: var(--on-surface, #000);
196
+ opacity: 0.7;
197
+ }
198
+
199
+ .list {
200
+ color: var(--primary-dark7);
201
+
202
+ & :global(.title) {
203
+ font-size: 0.7em !important;
204
+ font-weight: 300 !important;
205
+ margin-bottom: 4px;
206
+ }
207
+
208
+ & :global(.subtitle) {
209
+ font-size: 0.8em !important;
210
+ font-weight: 300 !important;
211
+ color: var(--primary-dark1);
212
+ }
213
+ }
214
+
215
+ .detail {
216
+ font-size: 0.8em;
217
+ padding: 8px;
218
+ display: flex;
219
+ flex-direction: column;
220
+ }
221
+
222
+ header {
223
+ display: flex;
224
+ align-items: center;
225
+ margin-bottom: 6px;
226
+
227
+ & > :global(*) {
228
+ margin-right: 8px;
229
+
230
+ &:last-child {
231
+ margin-right: 0;
232
+ }
233
+ }
234
+ }
235
+
236
+ footer {
237
+ margin-top: 0.6em;
238
+ display: flex;
239
+ justify-content: flex-end;
240
+ }
241
+ </style>
@@ -1,4 +1,3 @@
1
- declare const Command: import("svelte").Component<{
2
- selectedIndex?: number;
3
- }, {}, "">;
1
+ declare const Command: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Command = ReturnType<typeof Command>;
4
3
  export default Command;
@@ -2,11 +2,42 @@
2
2
  import { FloatingMenu } from 'svelte-tiptap';
3
3
  import { getContext } from 'svelte';
4
4
  import { IconButton, List, OneLine, Paper } from 'nunui';
5
+ import type { CommandProps } from '@tiptap/core';
6
+ import { NodeSelection } from '@tiptap/pm/state';
5
7
  import ToolbarButton from './ToolbarButton.svelte';
6
- import i18n from '../i18n';
8
+ import defaultI18n, { I18N_CONTEXT, type I18nTranslate } from '../i18n';
7
9
 
8
10
  const editor = getContext<{ v: any }>('editor');
11
+ const i18nFromContext = getContext<I18nTranslate | undefined>(I18N_CONTEXT);
12
+ const i18n: I18nTranslate = (...args) =>
13
+ i18nFromContext ? i18nFromContext(...args) : defaultI18n(...args);
9
14
  const tiptap = $derived(editor.v);
15
+
16
+ function insertMathInline() {
17
+ tiptap
18
+ .chain()
19
+ .focus()
20
+ .command(({ state, tr, dispatch }: CommandProps) => {
21
+ const { math_inline: mathInline } = state.schema.nodes;
22
+ if (!mathInline) return false;
23
+
24
+ const fromSelection = state.selection.$from;
25
+ const index = fromSelection.index();
26
+ if (!fromSelection.parent.canReplaceWith(index, index, mathInline)) return false;
27
+
28
+ const insertedPosition = fromSelection.pos;
29
+ const mathNode = mathInline.create({});
30
+ const nextTr = tr.replaceSelectionWith(mathNode);
31
+ nextTr.setSelection(NodeSelection.create(nextTr.doc, insertedPosition));
32
+ dispatch?.(nextTr);
33
+ return true;
34
+ })
35
+ .run();
36
+ }
37
+
38
+ function insertCodeBlock() {
39
+ tiptap.chain().focus().setCodeBlock({ language: null }).run();
40
+ }
10
41
  </script>
11
42
 
12
43
  {#if tiptap}
@@ -16,7 +47,7 @@
16
47
  <span>
17
48
  {i18n('newLineInfo')}
18
49
  </span>
19
- <Paper bl hover width="160px">
50
+ <Paper bl hover style="width: 160px">
20
51
  {#snippet target()}
21
52
  <IconButton size="1.2em" icon="text_fields" />
22
53
  {/snippet}
@@ -52,32 +83,28 @@
52
83
  <ToolbarButton
53
84
  icon="functions"
54
85
  handler={() => {
55
- const end = tiptap.state.selection.$to.pos;
56
- tiptap
57
- .chain()
58
- .focus()
59
- .insertContent({
60
- type: 'math_inline'
61
- })
62
- .insertContent(' ')
63
- .run();
86
+ insertMathInline();
64
87
  }}
65
88
  />
66
- <ToolbarButton icon="code" prop="code" />
89
+ <ToolbarButton icon="code" handler={insertCodeBlock} />
67
90
  </main>
68
91
  </FloatingMenu>
69
92
  {/if}
70
93
 
71
- <style>span {
72
- opacity: 0.6;
73
- }
94
+ <style>
95
+ span {
96
+ opacity: 0.6;
97
+ }
98
+
99
+ main {
100
+ display: flex;
101
+ flex-wrap: wrap;
102
+ width: max-content;
103
+ align-items: center;
104
+ position: relative;
74
105
 
75
- main {
76
- display: flex;
77
- flex-wrap: wrap;
78
- width: max-content;
79
- align-items: center;
80
- }
81
- main > :global(*) {
82
- margin-right: 4px;
83
- }</style>
106
+ & > :global(*) {
107
+ margin-right: 4px;
108
+ }
109
+ }
110
+ </style>
@@ -1,2 +1,3 @@
1
1
  declare const Floating: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Floating = ReturnType<typeof Floating>;
2
3
  export default Floating;