@seorii/tiptap 0.2.22 → 0.3.0-next.2

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.
@@ -1,45 +1,71 @@
1
- <script>import { FloatingMenu } from "svelte-tiptap";
2
- import { getContext } from "svelte";
3
- import { IconButton, List, OneLine, Tooltip } from "nunui";
4
- import ToolbarButton from "./ToolbarButton.svelte";
5
- import i18n from "../i18n";
6
- const tiptap = getContext('editor');
1
+ <script lang="ts">
2
+ import { FloatingMenu } from 'svelte-tiptap';
3
+ import { getContext } from 'svelte';
4
+ import { IconButton, List, OneLine, Paper } from 'nunui';
5
+ import ToolbarButton from './ToolbarButton.svelte';
6
+ import i18n from '../i18n';
7
+
8
+ const editor = getContext<{ v: any }>('editor');
9
+ const tiptap = $derived(editor.v);
7
10
  </script>
8
11
 
9
- {#if $tiptap}
10
- <FloatingMenu editor={$tiptap} tippyOptions={{animation:'fade', duration: [200, 50]}}>
11
- <main on:mousedown={() => setTimeout(() => $tiptap.commands.focus())}>
12
- <span>
13
- {i18n('newLineInfo')}
14
- </span>
15
- <Tooltip bottom left xstack width="160px">
16
- <IconButton size="1.2em" icon="text_fields" slot="target"/>
17
- <div style="margin: -6px">
18
- <List>
19
- <OneLine icon="counter_1" title="{i18n('title')} 1"
20
- on:click={() => $tiptap.commands.setHeading({level: 1})}/>
21
- <OneLine icon="counter_2" title="{i18n('title')} 2"
22
- on:click={() => $tiptap.commands.setHeading({level: 2})}/>
23
- <OneLine icon="counter_3" title="{i18n('title')} 3"
24
- on:click={() => $tiptap.commands.setHeading({level: 3})}/>
25
- <OneLine icon="segment" title={i18n('paragraph')}
26
- on:click={() => $tiptap.commands.setParagraph()}/>
27
- </List>
28
- </div>
29
- </Tooltip>
30
- <ToolbarButton icon="format_bold" prop="bold"/>
31
- <ToolbarButton icon="format_italic" prop="italic"/>
32
- <ToolbarButton icon="format_strikethrough" prop="strike"/>
33
- <ToolbarButton icon="format_underlined" prop="underline"/>
34
- <ToolbarButton icon="functions" handler={() => {
35
- const end = $tiptap.state.selection.$to.pos;
36
- $tiptap.chain().focus().insertContent({
37
- type: 'math_inline',
38
- }).insertContent(' ').run();
39
- }}/>
40
- <ToolbarButton icon="code" prop="code"/>
41
- </main>
42
- </FloatingMenu>
12
+ {#if tiptap}
13
+ <FloatingMenu editor={tiptap} tippyOptions={{ animation: 'fade', duration: [200, 50] }}>
14
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
15
+ <main onmousedown={() => setTimeout(() => tiptap.commands.focus())}>
16
+ <span>
17
+ {i18n('newLineInfo')}
18
+ </span>
19
+ <Paper bl hover width="160px">
20
+ {#snippet target()}
21
+ <IconButton size="1.2em" icon="text_fields" />
22
+ {/snippet}
23
+ <div style="margin: -6px">
24
+ <List>
25
+ <OneLine
26
+ icon="counter_1"
27
+ title="{i18n('title')} 1"
28
+ onclick={() => tiptap.commands.setHeading({ level: 1 })}
29
+ />
30
+ <OneLine
31
+ icon="counter_2"
32
+ title="{i18n('title')} 2"
33
+ onclick={() => tiptap.commands.setHeading({ level: 2 })}
34
+ />
35
+ <OneLine
36
+ icon="counter_3"
37
+ title="{i18n('title')} 3"
38
+ onclick={() => tiptap.commands.setHeading({ level: 3 })}
39
+ />
40
+ <OneLine
41
+ icon="segment"
42
+ title={i18n('paragraph')}
43
+ onclick={() => tiptap.commands.setParagraph()}
44
+ />
45
+ </List>
46
+ </div>
47
+ </Paper>
48
+ <ToolbarButton icon="format_bold" prop="bold" />
49
+ <ToolbarButton icon="format_italic" prop="italic" />
50
+ <ToolbarButton icon="format_strikethrough" prop="strike" />
51
+ <ToolbarButton icon="format_underlined" prop="underline" />
52
+ <ToolbarButton
53
+ icon="functions"
54
+ 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();
64
+ }}
65
+ />
66
+ <ToolbarButton icon="code" prop="code" />
67
+ </main>
68
+ </FloatingMenu>
43
69
  {/if}
44
70
 
45
71
  <style>span {
@@ -54,4 +80,4 @@ main {
54
80
  }
55
81
  main > :global(*) {
56
82
  margin-right: 4px;
57
- }</style>
83
+ }</style>
@@ -1,126 +1,203 @@
1
- <script>import { browser } from '$app/environment';
2
- import { beforeUpdate, onMount, setContext } from 'svelte';
3
- import { writable } from 'svelte/store';
4
- import sanitizeHtml from 'sanitize-html';
5
- import '@seorii/prosemirror-math/style.css';
6
- import Bubble from './Bubble.svelte';
7
- import Floating from './Floating.svelte';
8
- import Command from './Command.svelte';
9
- import { slashItems, slashProps, slashVisible } from '../plugin/command/stores';
10
- import i18n from '../i18n';
11
- import { fallbackUpload } from '../plugin/image/dragdrop';
12
- export let body = '', editable = false, mark = false, ref = null, options = {}, loaded = false;
13
- export let imageUpload = fallbackUpload, style = '';
14
- export let blocks = [], placeholder = i18n('placeholder');
15
- export let sanitize = {};
16
- export let colors = [
17
- '#ef5350', //red
18
- '#ec407a', //pink
19
- '#ff7043', //orange
20
- '#daca3b', //yellow
21
- '#8bc34a', //green
22
- '#2196f3', //blue
23
- '#3f51b5', //blue
24
- '#ab47bc' //purple
25
- ];
26
- export let bubbleOverride = false;
27
- const san = (body) => sanitizeHtml(body, {
28
- ...sanitize,
29
- allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'math-inline', 'math-node', 'iframe', 'lite-youtube', 'blockquote', 'embed', 'mark', 'code', ...sanitize.allowedTags]),
30
- allowedStyles: '*', allowedAttributes: {
31
- '*': ['style', 'class'],
32
- a: ['href', 'name', 'target'],
33
- img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
34
- iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
35
- th: ['colwidth', 'colspan', 'rowspan'],
36
- td: ['colwidth', 'colspan', 'rowspan'],
37
- 'lite-youtube': ['videoid', 'params', 'nocookie', 'title', 'provider'],
38
- embed: ['src', 'type', 'frameborder', 'allowfullscreen'],
39
- mark: ['style', 'data-color'],
40
- code: ['class'],
41
- ...sanitize.allowedAttributes
42
- }
43
- });
44
- const tiptap = setContext('editor', writable(null));
45
- let element, fullscreen = false, mounted = false, last = '';
46
- $: $tiptap && $tiptap.setEditable(editable);
47
- $: browser && (window.__image_uploader = imageUpload);
48
- $: browser && (window.__tiptap_blocks = blocks);
49
- if (browser) {
50
- onMount(() => {
51
- body = last = san(body);
52
- mounted = true;
53
- Promise.all([import('./tiptap'), import('@justinribeiro/lite-youtube')]).then(([{ default: tt }]) => {
54
- if (!mounted)
55
- return;
56
- ref = $tiptap = tt(element, body, {
57
- placeholder,
58
- editable: editable,
59
- onTransaction: () => ref = $tiptap = $tiptap,
60
- ...options
61
- });
62
- $tiptap.on('update', ({ editor: tiptap }) => {
63
- let content = tiptap.getHTML(), json = tiptap.getJSON().content;
64
- if (Array.isArray(json) && json.length === 1 && json[0].type === 'paragraph' && !json[0].hasOwnProperty('content'))
65
- content = null;
66
- body = last = content;
67
- });
68
- loaded = true;
69
- });
70
- return () => {
71
- mounted = false;
72
- $tiptap?.destroy?.();
73
- };
74
- });
75
- beforeUpdate(() => {
76
- if (last === body)
77
- return;
78
- body = san(body);
79
- $tiptap?.commands?.setContent?.(body);
80
- });
81
- }
82
- let selectedIndex = 0;
83
- $: selectedIndex = $slashVisible ? selectedIndex : 0;
84
- function handleKeydown(event) {
85
- if (!$slashVisible)
86
- return;
87
- let count = $slashItems.length;
88
- if ($slashItems[0]?.list)
89
- count = $slashItems.reduce((acc, item) => acc + item.list.length, 0);
90
- if (event.key === 'ArrowUp') {
91
- event.preventDefault();
92
- selectedIndex = (selectedIndex + count - 1) % count;
93
- return true;
94
- }
95
- if (event.key === 'ArrowDown') {
96
- event.preventDefault();
97
- selectedIndex = (selectedIndex + 1) % count;
98
- return true;
99
- }
100
- if (event.key === 'Enter') {
101
- event.preventDefault();
102
- selectItem(selectedIndex);
103
- return true;
104
- }
105
- return false;
106
- }
107
- function selectItem(index) {
108
- const item = $slashItems[0]?.list ? $slashItems.map(i => i.list).flat()[index] : $slashItems[index];
109
- if (item) {
110
- let range = $slashProps.range;
111
- item.command({ editor: $tiptap, range });
112
- }
113
- }
1
+ <script lang="ts">
2
+ import { browser } from '$app/environment';
3
+ import { onMount, onDestroy, setContext } from 'svelte';
4
+ import sanitizeHtml from 'sanitize-html';
5
+ import '@seorii/prosemirror-math/style.css';
6
+ import Bubble from './Bubble.svelte';
7
+ import Floating from './Floating.svelte';
8
+ import Command from './Command.svelte';
9
+ import { slashItems, slashProps, slashVisible } from '../plugin/command/stores';
10
+ import i18n from '../i18n';
11
+ import type { UploadFn } from '../plugin/image/dragdrop';
12
+ import { fallbackUpload } from '../plugin/image/dragdrop';
13
+ import { Render } from 'nunui';
14
+
15
+ type Props = {
16
+ body: string;
17
+ editable: boolean;
18
+ mark: boolean;
19
+ ref: any;
20
+ options: Record<string, any>;
21
+ loaded: boolean;
22
+ imageUpload: UploadFn;
23
+ style: string;
24
+ blocks: any[];
25
+ placeholder: string;
26
+ sanitize: Record<string, any>;
27
+ colors: string[];
28
+ bubble?: any;
29
+ preloader?: any;
30
+ };
31
+
32
+ let {
33
+ body = $bindable(''),
34
+ editable = false,
35
+ mark = false,
36
+ ref = null,
37
+ options = {},
38
+ loaded = false,
39
+ imageUpload = fallbackUpload,
40
+ style = '',
41
+ blocks = [],
42
+ placeholder = i18n('placeholder'),
43
+ sanitize = {},
44
+ colors = [
45
+ '#ef5350', //red
46
+ '#ec407a', //pink
47
+ '#ff7043', //orange
48
+ '#daca3b', //yellow
49
+ '#8bc34a', //green
50
+ '#2196f3', //blue
51
+ '#3f51b5', //blue
52
+ '#ab47bc' //purple
53
+ ],
54
+ bubble = null,
55
+ preloader
56
+ }: Props = $props();
57
+
58
+ const san = (body: string) =>
59
+ sanitizeHtml(body, {
60
+ ...(sanitize || {}),
61
+ allowedTags: sanitizeHtml.defaults.allowedTags.concat([
62
+ 'img',
63
+ 'math-inline',
64
+ 'math-node',
65
+ 'iframe',
66
+ 'lite-youtube',
67
+ 'blockquote',
68
+ 'embed',
69
+ 'mark',
70
+ 'code',
71
+ ...(sanitize.allowedTags || [])
72
+ ]),
73
+ allowedStyles: '*' as any,
74
+ allowedAttributes: {
75
+ '*': ['style', 'class'],
76
+ a: ['href', 'name', 'target'],
77
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
78
+ iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
79
+ th: ['colwidth', 'colspan', 'rowspan'],
80
+ td: ['colwidth', 'colspan', 'rowspan'],
81
+ 'lite-youtube': ['videoid', 'params', 'nocookie', 'title', 'provider'],
82
+ embed: ['src', 'type', 'frameborder', 'allowfullscreen'],
83
+ mark: ['style', 'data-color'],
84
+ code: ['class'],
85
+ ...(sanitize.allowedAttributes || [])
86
+ }
87
+ });
88
+
89
+ const tiptap = $state({ v: null as any });
90
+ setContext('editor', tiptap);
91
+ let element: Element,
92
+ fullscreen = $state(false),
93
+ mounted = $state(false),
94
+ last = $state('');
95
+
96
+ $effect(() => {
97
+ if (tiptap.v) tiptap.v.setEditable(editable);
98
+ });
99
+
100
+ $effect(() => {
101
+ if (browser) {
102
+ (window as any).__image_uploader = imageUpload;
103
+ (window as any).__tiptap_blocks = blocks;
104
+ }
105
+ });
106
+
107
+ if (browser)
108
+ onMount(() => {
109
+ body = last = san(body);
110
+ mounted = true;
111
+ Promise.all([import('./tiptap'), import('@justinribeiro/lite-youtube')]).then(
112
+ ([{ default: tt }]) => {
113
+ if (!mounted) return;
114
+ tiptap.v = ref = tt(element, body, {
115
+ placeholder,
116
+ editable,
117
+ onTransaction: () => (tiptap.v = ref = tiptap.v),
118
+ ...options
119
+ });
120
+ tiptap.v.on('update', ({ editor: tiptap }: any) => {
121
+ let content = tiptap.getHTML(),
122
+ json = tiptap.getJSON().content;
123
+ if (
124
+ Array.isArray(json) &&
125
+ json.length === 1 &&
126
+ json[0].type === 'paragraph' &&
127
+ !json[0].hasOwnProperty('content')
128
+ )
129
+ content = null;
130
+ body = last = content;
131
+ });
132
+ loaded = true;
133
+ }
134
+ );
135
+ });
136
+
137
+ onDestroy(() => {
138
+ mounted = false;
139
+ tiptap.v?.destroy?.();
140
+ });
141
+
142
+ $effect.pre(() => {
143
+ if (last === body) return;
144
+ body = san(body);
145
+ tiptap.v?.commands?.setContent?.(body);
146
+ });
147
+
148
+ let selectedIndex = $state(0);
149
+ $effect(() => {
150
+ if (!slashVisible) selectedIndex = 0;
151
+ });
152
+
153
+ function handleKeydown(event: KeyboardEvent) {
154
+ if (!$slashVisible) return;
155
+ let count = $slashItems.length;
156
+ if ($slashItems[0]?.list) count = $slashItems.reduce((acc, item) => acc + item.list.length, 0);
157
+ if (event.key === 'ArrowUp') {
158
+ event.preventDefault();
159
+ selectedIndex = (selectedIndex + count - 1) % count;
160
+ return true;
161
+ }
162
+ if (event.key === 'ArrowDown') {
163
+ event.preventDefault();
164
+ selectedIndex = (selectedIndex + 1) % count;
165
+ return true;
166
+ }
167
+
168
+ if (event.key === 'Enter') {
169
+ event.preventDefault();
170
+ selectItem(selectedIndex);
171
+ return true;
172
+ }
173
+
174
+ return false;
175
+ }
176
+
177
+ function selectItem(index) {
178
+ const item = $slashItems[0]?.list
179
+ ? $slashItems.map((i) => i.list).flat()[index]
180
+ : $slashItems[index];
181
+ if (item) {
182
+ let range = $slashProps.range;
183
+ item.command({ editor: tiptap.v, range });
184
+ }
185
+ }
114
186
  </script>
115
187
 
116
- <main class:fullscreen class:editable>
188
+ <main class:fullscreen class:editable {style}>
117
189
  <div class="wrapper">
118
- <div bind:this={element} class="target" on:keydown|capture={handleKeydown}></div>
119
- {#if !$tiptap}
120
- {#if $$slots.preloader}
121
- <slot name="preloader" />
190
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
191
+ <div bind:this={element} class="target" onkeydown={handleKeydown}></div>
192
+ {#if !tiptap.v}
193
+ {#if preloader}
194
+ <Render it={preloader} />
122
195
  {:else}
123
- {i18n('loading')}
196
+ <div class="target">
197
+ <div>
198
+ {@html san(body)}
199
+ </div>
200
+ </div>
124
201
  {/if}
125
202
  {/if}
126
203
  </div>
@@ -129,23 +206,18 @@ function selectItem(index) {
129
206
  <Floating />
130
207
  {/if}
131
208
  {#if editable || mark}
132
- <Bubble {colors} {editable} override={bubbleOverride}>
133
- <slot name="bubble" />
134
- <slot name="bubbleOverride" slot="override" />
209
+ <Bubble {colors} {editable} override={bubble}>
210
+ <Render it={bubble} />
135
211
  </Bubble>
136
212
  {/if}
137
213
  </main>
138
214
 
139
-
140
215
  <style>main {
141
216
  position: relative;
142
217
  overscroll-behavior: none;
143
- --shadow: 0 1px 2px rgba(127, 127, 127, 0.07),
144
- 0 2px 4px rgba(127, 127, 127, 0.07),
145
- 0 4px 8px rgba(127, 127, 127, 0.07),
146
- 0 8px 16px rgba(127, 127, 127, 0.07),
147
- 0 16px 32px rgba(127, 127, 127, 0.07),
148
- 0 32px 64px rgba(127, 127, 127, 0.07);
218
+ --shadow: 0 1px 2px rgba(127, 127, 127, 0.07), 0 2px 4px rgba(127, 127, 127, 0.07),
219
+ 0 4px 8px rgba(127, 127, 127, 0.07), 0 8px 16px rgba(127, 127, 127, 0.07),
220
+ 0 16px 32px rgba(127, 127, 127, 0.07), 0 32px 64px rgba(127, 127, 127, 0.07);
149
221
  }
150
222
  main.fullscreen {
151
223
  z-index: 999999999;
@@ -223,7 +295,8 @@ div > :global(div) :global(table) {
223
295
  border: 1px solid var(--primary-light1);
224
296
  border-radius: 12px;
225
297
  }
226
- div > :global(div) :global(table) :global(th), div > :global(div) :global(table) :global(td) {
298
+ div > :global(div) :global(th),
299
+ div > :global(div) :global(td) {
227
300
  padding: 8px;
228
301
  border: 1px solid var(--primary-light1);
229
302
  }
@@ -241,10 +314,10 @@ div > :global(div) :global(.iframe-wrapper) {
241
314
  height: 600px;
242
315
  border-radius: 12px;
243
316
  }
244
- div > :global(div) :global(.iframe-wrapper) :global(iframe) {
317
+ div > :global(div) :global(iframe) {
245
318
  position: absolute;
246
319
  top: 0;
247
320
  left: 0;
248
321
  width: 100%;
249
322
  height: 100%;
250
- }</style>
323
+ }</style>
@@ -1,32 +1,11 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import '@seorii/prosemirror-math/style.css';
3
- import type { UploadFn } from '../plugin/image/dragdrop';
4
3
  declare const __propDef: {
5
- props: {
6
- body?: string | undefined;
7
- editable?: boolean | undefined;
8
- mark?: boolean | undefined;
9
- ref?: null | undefined;
10
- options?: {} | undefined;
11
- loaded?: boolean | undefined;
12
- imageUpload?: UploadFn | undefined;
13
- style?: string | undefined;
14
- blocks?: any[] | undefined;
15
- placeholder?: any;
16
- sanitize?: any;
17
- colors?: string[] | undefined;
18
- bubbleOverride?: boolean | undefined;
19
- };
4
+ props: Record<string, never>;
20
5
  events: {
21
6
  [evt: string]: CustomEvent<any>;
22
7
  };
23
- slots: {
24
- preloader: {};
25
- bubble: {};
26
- bubbleOverride: {
27
- slot: string;
28
- };
29
- };
8
+ slots: {};
30
9
  };
31
10
  export type TipTapProps = typeof __propDef.props;
32
11
  export type TipTapEvents = typeof __propDef.events;
@@ -1,26 +1,30 @@
1
- <script>import { getContext } from "svelte";
2
- import { Button, IconButton } from "nunui";
3
- const editor = getContext("editor");
4
- export let prop = '', attrs = '', label = '', icon = '', methodName = 'toggle' + prop.charAt(0).toUpperCase() + prop.slice(1), tooltip, handler;
5
- $: isActive = () => {
6
- return editor && prop && $editor.isActive(prop, attrs);
7
- };
8
- function toggle() {
9
- if (!$editor)
10
- return;
11
- //$editor.chain().focus().clearNodes().run()
12
- if (handler)
13
- return handler();
14
- setTimeout(() => $editor.chain().focus()[methodName](attrs)?.run(), 0);
15
- }
1
+ <script lang="ts">
2
+ import {getContext} from "svelte";
3
+ import {Button, IconButton, Render} from "nunui";
4
+
5
+ const editor = getContext<{ v: any }>('editor');
6
+ const tiptap = $derived(editor.v);
7
+
8
+ let {prop = '', attrs = '', label = '', icon = '', methodName = 'toggle' + prop.charAt(0).toUpperCase() + prop.slice(1), tooltip, handler, ...rest} = $props();
9
+
10
+ const isActive = $derived(() => {
11
+ return editor && prop && tiptap.isActive(prop, attrs);
12
+ });
13
+
14
+ function toggle() {
15
+ if (!tiptap) return
16
+ //tiptap.chain().focus().clearNodes().run()
17
+ if(handler) return handler()
18
+ setTimeout(() => tiptap.chain().focus()[methodName](attrs)?.run(), 0)
19
+ }
16
20
  </script>
17
21
 
18
22
  {#if icon}
19
- <IconButton size="1.2em" {icon} active={isActive()} on:click={toggle} tooltip={tooltip} tabindex="0"/>
23
+ <IconButton size="1.2em" {icon} active={isActive()} onclick={toggle} tooltip={tooltip} tabindex="0"/>
20
24
  {:else}
21
- <Button outlined={!isActive()} on:click={handler || toggle} small {...$$restProps}>
25
+ <Button outlined={!isActive()} onclick={handler || toggle} small {...rest}>
22
26
  {label}
23
- <slot/>
27
+ <Render it={children} />
24
28
  </Button>
25
29
  {/if}
26
30
 
@@ -1,21 +1,10 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
- props: {
4
- [x: string]: any;
5
- prop?: string | undefined;
6
- attrs?: string | undefined;
7
- label?: string | undefined;
8
- icon?: string | undefined;
9
- methodName?: string | undefined;
10
- tooltip: any;
11
- handler: any;
12
- };
3
+ props: Record<string, never>;
13
4
  events: {
14
5
  [evt: string]: CustomEvent<any>;
15
6
  };
16
- slots: {
17
- default: {};
18
- };
7
+ slots: {};
19
8
  };
20
9
  export type ToolbarButtonProps = typeof __propDef.props;
21
10
  export type ToolbarButtonEvents = typeof __propDef.events;
@@ -1,3 +1,3 @@
1
- import { Editor } from "@tiptap/core";
1
+ import { Editor } from '@tiptap/core';
2
2
  declare const _default: (element: Element, content: string, { placeholder, plugins, ...props }?: any) => Editor;
3
3
  export default _default;