@seorii/tiptap 0.1.2 → 0.2.1
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.
- package/dist/plugin/command/emoji.d.ts +20 -0
- package/dist/plugin/command/emoji.js +81 -0
- package/dist/plugin/command/stores.d.ts +1 -0
- package/dist/plugin/command/stores.js +1 -0
- package/dist/plugin/command/suggest.d.ts +7 -10
- package/dist/plugin/command/suggest.js +47 -11
- package/dist/plugin/table/style/grip.scss +1 -1
- package/dist/plugin/table/style/resize.scss +2 -2
- package/dist/plugin/table/style/table.scss +1 -1
- package/dist/plugin/table/style.scss +0 -1
- package/dist/tiptap/Command.svelte +44 -19
- package/dist/tiptap/TipTap.svelte +10 -5
- package/dist/tiptap/TipTap.svelte.d.ts +2 -3
- package/dist/tiptap/tiptap.js +56 -51
- package/package.json +3 -1
- package/dist/plugin/command/index.d.ts +0 -4
- package/dist/plugin/command/index.js +0 -27
- package/dist/plugin/table/style/theme.scss +0 -6
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PluginKey } from "prosemirror-state";
|
|
2
|
+
import type { Editor } from "@tiptap/core";
|
|
3
|
+
export declare const emoji: {
|
|
4
|
+
pluginKey: PluginKey<any>;
|
|
5
|
+
char: string;
|
|
6
|
+
items: ({ query }: {
|
|
7
|
+
query: any;
|
|
8
|
+
}) => {
|
|
9
|
+
title: string;
|
|
10
|
+
command: {};
|
|
11
|
+
}[];
|
|
12
|
+
render: () => {
|
|
13
|
+
onStart: (props: any) => void;
|
|
14
|
+
onUpdate(props: any): void;
|
|
15
|
+
onKeyDown(props: any): true | undefined;
|
|
16
|
+
onExit(): void;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
declare const _default: (editor: Editor) => import("prosemirror-state").Plugin<any>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail } from './stores';
|
|
2
|
+
import { PluginKey } from "prosemirror-state";
|
|
3
|
+
import Suggestion from "@tiptap/suggestion";
|
|
4
|
+
//@ts-ignore
|
|
5
|
+
import emojis from 'emojis-list';
|
|
6
|
+
//@ts-ignore
|
|
7
|
+
import tags from 'emojis-keywords';
|
|
8
|
+
const max = 10;
|
|
9
|
+
function fixRange(editor, range, split = '/') {
|
|
10
|
+
const { state } = editor.view, { selection, doc } = state;
|
|
11
|
+
if (selection.$to.nodeBefore?.text?.includes?.(split)) {
|
|
12
|
+
range.from = range.to;
|
|
13
|
+
while (range.from > 0 && doc.textBetween(range.from - 1, range.from) !== split) {
|
|
14
|
+
try {
|
|
15
|
+
range.from -= 1;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
range.from += 2;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
range.from -= 1;
|
|
23
|
+
}
|
|
24
|
+
while (range.to < selection.to && doc.textBetween(range.to, range.to + 1) !== ' ') {
|
|
25
|
+
try {
|
|
26
|
+
range.to += 1;
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
range.to -= 1;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return range;
|
|
34
|
+
}
|
|
35
|
+
export const emoji = {
|
|
36
|
+
pluginKey: new PluginKey('slash-emoji'),
|
|
37
|
+
char: ':',
|
|
38
|
+
items: ({ query }) => {
|
|
39
|
+
query = ':' + query.toLowerCase();
|
|
40
|
+
const filtered = [];
|
|
41
|
+
for (let i = 0; i < emojis.length; i++) {
|
|
42
|
+
if (tags[i]?.includes?.(query))
|
|
43
|
+
filtered.push({
|
|
44
|
+
title: emojis[i] + ' ' + tags[i],
|
|
45
|
+
command: ({ editor, range }) => {
|
|
46
|
+
editor.chain().deleteRange(fixRange(editor, range, ':')).insertContent(emojis[i] + ' ').run();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
if (filtered.length >= max)
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
return filtered;
|
|
53
|
+
},
|
|
54
|
+
render: () => {
|
|
55
|
+
return {
|
|
56
|
+
onStart: (props) => {
|
|
57
|
+
let editor = props.editor;
|
|
58
|
+
let range = props.range;
|
|
59
|
+
let location = props.clientRect();
|
|
60
|
+
slashProps.set({ editor, range });
|
|
61
|
+
slashVisible.set(true);
|
|
62
|
+
slashLocaltion.set({ x: location.x, y: location.y, height: location.height });
|
|
63
|
+
slashItems.set(props.items);
|
|
64
|
+
slashDetail.set('emoji');
|
|
65
|
+
},
|
|
66
|
+
onUpdate(props) {
|
|
67
|
+
slashItems.set(props.items);
|
|
68
|
+
},
|
|
69
|
+
onKeyDown(props) {
|
|
70
|
+
if (props.event.key === 'Escape') {
|
|
71
|
+
slashVisible.set(false);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
onExit() {
|
|
76
|
+
slashVisible.set(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export default (editor) => Suggestion({ ...emoji, editor });
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { PluginKey } from "prosemirror-state";
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
export declare const suggest: {
|
|
4
|
+
pluginKey: PluginKey<any>;
|
|
5
|
+
char: string;
|
|
2
6
|
items: ({ query }: {
|
|
3
7
|
query: any;
|
|
4
8
|
}) => {
|
|
5
9
|
section: any;
|
|
6
|
-
list:
|
|
7
|
-
icon: string;
|
|
8
|
-
title: any;
|
|
9
|
-
subtitle: any;
|
|
10
|
-
command: ({ editor, range }: {
|
|
11
|
-
editor: any;
|
|
12
|
-
range: any;
|
|
13
|
-
}) => void;
|
|
14
|
-
}[];
|
|
10
|
+
list: any[];
|
|
15
11
|
}[];
|
|
16
12
|
render: () => {
|
|
17
13
|
onStart: (props: any) => void;
|
|
@@ -20,4 +16,5 @@ declare const _default: {
|
|
|
20
16
|
onExit(): void;
|
|
21
17
|
};
|
|
22
18
|
};
|
|
19
|
+
declare const _default: (editor: Editor) => import("prosemirror-state").Plugin<any>;
|
|
23
20
|
export default _default;
|
|
@@ -1,7 +1,38 @@
|
|
|
1
|
-
import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail } from './stores';
|
|
1
|
+
import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail, slashSelection } from './stores';
|
|
2
2
|
import i18n from "../../i18n";
|
|
3
3
|
import { fallbackUpload } from "../image/dragdrop";
|
|
4
|
-
|
|
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;
|
|
9
|
+
if (selection.$to.nodeBefore?.text?.includes?.(split)) {
|
|
10
|
+
range.from = range.to;
|
|
11
|
+
while (range.from > 0 && doc.textBetween(range.from - 1, range.from) !== split) {
|
|
12
|
+
try {
|
|
13
|
+
range.from -= 1;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
range.from += 2;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
range.from -= 1;
|
|
21
|
+
}
|
|
22
|
+
while (range.to < selection.to && doc.textBetween(range.to, range.to + 1) !== ' ') {
|
|
23
|
+
try {
|
|
24
|
+
range.to += 1;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
range.to -= 1;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return range;
|
|
32
|
+
}
|
|
33
|
+
export const suggest = {
|
|
34
|
+
pluginKey: new PluginKey('slash-suggest'),
|
|
35
|
+
char: '/',
|
|
5
36
|
items: ({ query }) => {
|
|
6
37
|
const raw = [
|
|
7
38
|
{
|
|
@@ -11,7 +42,7 @@ export default {
|
|
|
11
42
|
title: i18n('title') + ' 1',
|
|
12
43
|
subtitle: i18n('title1Info'),
|
|
13
44
|
command: ({ editor, range }) => {
|
|
14
|
-
editor.chain().focus().deleteRange(range).setNode('heading', { level: 1 }).run();
|
|
45
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setNode('heading', { level: 1 }).run();
|
|
15
46
|
}
|
|
16
47
|
},
|
|
17
48
|
{
|
|
@@ -19,7 +50,7 @@ export default {
|
|
|
19
50
|
title: i18n('title') + ' 2',
|
|
20
51
|
subtitle: i18n('title2Info'),
|
|
21
52
|
command: ({ editor, range }) => {
|
|
22
|
-
editor.chain().focus().deleteRange(range).setNode('heading', { level: 2 }).run();
|
|
53
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setNode('heading', { level: 2 }).run();
|
|
23
54
|
}
|
|
24
55
|
},
|
|
25
56
|
{
|
|
@@ -27,7 +58,7 @@ export default {
|
|
|
27
58
|
title: i18n('title') + ' 3',
|
|
28
59
|
subtitle: i18n('title3Info'),
|
|
29
60
|
command: ({ editor, range }) => {
|
|
30
|
-
editor.chain().focus().deleteRange(range).setNode('heading', { level: 3 }).run();
|
|
61
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setNode('heading', { level: 3 }).run();
|
|
31
62
|
}
|
|
32
63
|
},
|
|
33
64
|
{
|
|
@@ -35,7 +66,7 @@ export default {
|
|
|
35
66
|
title: i18n('unorderedList'),
|
|
36
67
|
subtitle: i18n('unorderedListInfo'),
|
|
37
68
|
command: ({ editor, range }) => {
|
|
38
|
-
editor.commands.deleteRange(range);
|
|
69
|
+
editor.commands.deleteRange(fixRange(editor, range));
|
|
39
70
|
editor.commands.toggleBulletList();
|
|
40
71
|
}
|
|
41
72
|
},
|
|
@@ -44,7 +75,7 @@ export default {
|
|
|
44
75
|
title: i18n('numberList'),
|
|
45
76
|
subtitle: i18n('numberListInfo'),
|
|
46
77
|
command: ({ editor, range }) => {
|
|
47
|
-
editor.commands.deleteRange(range);
|
|
78
|
+
editor.commands.deleteRange(fixRange(editor, range));
|
|
48
79
|
editor.commands.toggleOrderedList();
|
|
49
80
|
}
|
|
50
81
|
}
|
|
@@ -52,11 +83,13 @@ export default {
|
|
|
52
83
|
},
|
|
53
84
|
{
|
|
54
85
|
section: i18n('block'), list: [
|
|
86
|
+
...(window.__tiptap_blocks),
|
|
55
87
|
{
|
|
56
88
|
icon: 'image',
|
|
57
89
|
title: i18n('image'),
|
|
58
90
|
subtitle: i18n('imageInfo'),
|
|
59
91
|
command: ({ editor, range }) => {
|
|
92
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).run();
|
|
60
93
|
const input = document.createElement('input');
|
|
61
94
|
input.type = 'file';
|
|
62
95
|
input.accept = 'image/*';
|
|
@@ -78,7 +111,7 @@ export default {
|
|
|
78
111
|
title: i18n('codeBlock'),
|
|
79
112
|
subtitle: i18n('codeBlockInfo'),
|
|
80
113
|
command: ({ editor, range }) => {
|
|
81
|
-
editor.chain().focus().deleteRange(range).setNode('codeBlock').run();
|
|
114
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setNode('codeBlock').run();
|
|
82
115
|
}
|
|
83
116
|
},
|
|
84
117
|
{
|
|
@@ -87,7 +120,7 @@ export default {
|
|
|
87
120
|
subtitle: i18n('mathBlockInfo'),
|
|
88
121
|
command: ({ editor, range }) => {
|
|
89
122
|
const { to } = range;
|
|
90
|
-
editor.chain().focus().deleteRange(range).setNode('math_display').focus().run();
|
|
123
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setNode('math_display').focus().run();
|
|
91
124
|
}
|
|
92
125
|
},
|
|
93
126
|
{
|
|
@@ -95,7 +128,7 @@ export default {
|
|
|
95
128
|
title: i18n('table'),
|
|
96
129
|
subtitle: i18n('tableInfo'),
|
|
97
130
|
command: ({ editor, range }) => {
|
|
98
|
-
editor.chain().focus().insertTable({ rows: 2, cols: 3 }).run();
|
|
131
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).insertTable({ rows: 2, cols: 3 }).run();
|
|
99
132
|
}
|
|
100
133
|
},
|
|
101
134
|
{
|
|
@@ -103,7 +136,7 @@ export default {
|
|
|
103
136
|
title: i18n('blockquote'),
|
|
104
137
|
subtitle: i18n('blockquoteInfo'),
|
|
105
138
|
command: ({ editor, range }) => {
|
|
106
|
-
editor.chain().focus().deleteRange(range).setBlockquote().focus().run();
|
|
139
|
+
editor.chain().focus().deleteRange(fixRange(editor, range)).setBlockquote().focus().run();
|
|
107
140
|
}
|
|
108
141
|
},
|
|
109
142
|
{
|
|
@@ -111,6 +144,7 @@ export default {
|
|
|
111
144
|
title: i18n('iframe'),
|
|
112
145
|
subtitle: i18n('iframeInfo'),
|
|
113
146
|
command: ({ editor, range }) => {
|
|
147
|
+
slashSelection.set(() => editor.chain().focus().deleteRange(fixRange(editor, range)).run());
|
|
114
148
|
slashDetail.set('iframe');
|
|
115
149
|
}
|
|
116
150
|
},
|
|
@@ -119,6 +153,7 @@ export default {
|
|
|
119
153
|
title: i18n('youtube'),
|
|
120
154
|
subtitle: i18n('youtubeInfo'),
|
|
121
155
|
command: ({ editor, range }) => {
|
|
156
|
+
slashSelection.set(() => editor.chain().focus().deleteRange(fixRange(editor, range)).run());
|
|
122
157
|
slashDetail.set('youtube');
|
|
123
158
|
}
|
|
124
159
|
}
|
|
@@ -158,3 +193,4 @@ export default {
|
|
|
158
193
|
};
|
|
159
194
|
}
|
|
160
195
|
};
|
|
196
|
+
export default (editor) => Suggestion({ ...suggest, editor });
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
background-color: var(--primary-light6);
|
|
10
10
|
opacity: 0;
|
|
11
11
|
|
|
12
|
-
.
|
|
12
|
+
.editable & {
|
|
13
13
|
opacity: 1;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
&.resize-cursor {
|
|
18
18
|
pointer-events: none;
|
|
19
19
|
|
|
20
|
-
.
|
|
20
|
+
.editable & {
|
|
21
21
|
pointer-events: initial;
|
|
22
22
|
cursor: ew-resize;
|
|
23
23
|
cursor: col-resize; /* stylelint-disable declaration-block-no-duplicate-properties */
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
<script>import { Button, IconButton, Input, List, TwoLine } from "nunui";
|
|
1
|
+
<script>import { Button, IconButton, Input, List, OneLine, TwoLine } from "nunui";
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
-
import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail } from '../plugin/command/stores';
|
|
3
|
+
import { slashVisible, slashItems, slashLocaltion, slashProps, slashDetail, slashSelection } from '../plugin/command/stores';
|
|
4
4
|
import { fly, slide } from "svelte/transition";
|
|
5
5
|
import { quartOut } from "svelte/easing";
|
|
6
6
|
import i18n from "../i18n";
|
|
7
7
|
const tiptap = getContext('editor');
|
|
8
8
|
export let selectedIndex = 0;
|
|
9
9
|
let height = 0, elements = [];
|
|
10
|
-
let iframe = '';
|
|
10
|
+
let iframe = '', focus;
|
|
11
11
|
$: if ($slashVisible) {
|
|
12
12
|
iframe = '';
|
|
13
13
|
}
|
|
14
|
+
$: setTimeout(() => focus?.focus?.(), 100);
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<svelte:window bind:innerHeight={height}/>
|
|
@@ -26,16 +27,19 @@ $: if ($slashVisible) {
|
|
|
26
27
|
<IconButton icon="arrow_back" on:click={() => $slashDetail = ''}/>
|
|
27
28
|
<div class="title">iframe</div>
|
|
28
29
|
</header>
|
|
29
|
-
<Input placeholder="url" fullWidth bind:value={iframe}
|
|
30
|
-
on:submit={() => $tiptap.
|
|
30
|
+
<Input placeholder="url" fullWidth bind:value={iframe} bind:input={focus}
|
|
31
|
+
on:submit={() => $tiptap.chain().focus().insertContent({type: 'iframe', attrs: {src: iframe}}).insertContent('\n').run()}/>
|
|
31
32
|
<footer>
|
|
32
33
|
<Button tabindex="0" transparent small on:click={() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
iframe = ''
|
|
35
|
+
$slashDetail = ''
|
|
36
|
+
}}>{i18n('cancel')}
|
|
36
37
|
</Button>
|
|
37
38
|
<Button tabindex="0" transparent small
|
|
38
|
-
on:click={() =>
|
|
39
|
+
on:click={() => {
|
|
40
|
+
$slashSelection?.();
|
|
41
|
+
$tiptap.chain().focus().insertContent({type: 'iframe', attrs: {src: iframe}}).insertContent('\n').run();
|
|
42
|
+
}}>{i18n('insert')}
|
|
39
43
|
</Button>
|
|
40
44
|
</footer>
|
|
41
45
|
</div>
|
|
@@ -45,31 +49,52 @@ $: if ($slashVisible) {
|
|
|
45
49
|
<IconButton icon="arrow_back" on:click={() => $slashDetail = ''}/>
|
|
46
50
|
<div class="title">Youtube</div>
|
|
47
51
|
</header>
|
|
48
|
-
<Input placeholder="url" fullWidth bind:value={iframe}
|
|
49
|
-
on:submit={() => $tiptap.
|
|
52
|
+
<Input placeholder="url" fullWidth bind:value={iframe} bind:input={focus}
|
|
53
|
+
on:submit={() => $tiptap.chain().focus().insertVideoPlayer({url: iframe}).insertContent('\n').run()}/>
|
|
50
54
|
<footer>
|
|
51
55
|
<Button tabindex="0" transparent small on:click={() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
iframe = ''
|
|
57
|
+
$slashDetail = ''
|
|
58
|
+
}}>{i18n('cancel')}
|
|
55
59
|
</Button>
|
|
56
|
-
<Button tabindex="0" transparent small
|
|
57
|
-
|
|
60
|
+
<Button tabindex="0" transparent small on:click={() => {
|
|
61
|
+
$slashSelection?.();
|
|
62
|
+
$tiptap.chain().focus().insertVideoPlayer({url: iframe}).insertContent('\n').run();
|
|
63
|
+
}}>{i18n('insert')}
|
|
58
64
|
</Button>
|
|
59
65
|
</footer>
|
|
60
66
|
</div>
|
|
67
|
+
{:else if $slashDetail === 'emoji'}
|
|
68
|
+
<div class="list">
|
|
69
|
+
<List>
|
|
70
|
+
{#each $slashItems as {title, command}, i(title)}
|
|
71
|
+
<div transition:slide={{duration: 400, easing: quartOut}}>
|
|
72
|
+
<OneLine on:click={() => {
|
|
73
|
+
command?.($slashProps);
|
|
74
|
+
setTimeout(() => $tiptap.commands.focus());
|
|
75
|
+
}} bind:this={elements[i]} {title} active={selectedIndex === i}/>
|
|
76
|
+
</div>
|
|
77
|
+
{/each}
|
|
78
|
+
{#if !$slashItems.length}
|
|
79
|
+
<div class="section"
|
|
80
|
+
transition:slide={{duration: 400, easing: quartOut}}>{i18n('noResult')}</div>
|
|
81
|
+
{/if}
|
|
82
|
+
</List>
|
|
83
|
+
</div>
|
|
61
84
|
{:else}
|
|
62
85
|
<div class="list">
|
|
63
86
|
<List>
|
|
64
|
-
{#each $slashItems as {section, list}(section)}
|
|
87
|
+
{#each $slashItems as {section, list}, j(section)}
|
|
88
|
+
{@const lastCount = $slashItems.slice(0, j).reduce((acc, cur) => acc + cur.list.length, 0)}
|
|
65
89
|
<div class="section" transition:slide={{duration: 400, easing: quartOut}}>{section}</div>
|
|
66
90
|
<div transition:slide={{duration: 400, easing: quartOut}}>
|
|
67
91
|
{#each list || [] as {title, subtitle, icon, command, section}, i(title)}
|
|
68
92
|
<div transition:slide={{duration: 400, easing: quartOut}}>
|
|
69
|
-
<TwoLine on:mouseenter={() => (selectedIndex = i)} on:click={() => {
|
|
93
|
+
<TwoLine on:mouseenter={() => (selectedIndex = i + lastCount)} on:click={() => {
|
|
70
94
|
command?.($slashProps);
|
|
71
95
|
setTimeout(() => $tiptap.commands.focus());
|
|
72
|
-
}} bind:this={elements[i]} {icon} {title} subtitle={subtitle || ''}
|
|
96
|
+
}} bind:this={elements[i + lastCount]} {icon} {title} subtitle={subtitle || ''}
|
|
97
|
+
active={selectedIndex === i + lastCount}/>
|
|
73
98
|
</div>
|
|
74
99
|
{/each}
|
|
75
100
|
</div>
|
|
@@ -23,11 +23,13 @@ const san = (body) => sanitizeHtml(body, {
|
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
25
|
export let body = '', editable = false, ref = null, options = {};
|
|
26
|
-
export
|
|
26
|
+
export let imageUpload = fallbackUpload, style = '';
|
|
27
|
+
export let blocks = [];
|
|
27
28
|
const tiptap = setContext('editor', writable(null));
|
|
28
29
|
let element, fullscreen = false, mounted = false, last = '';
|
|
29
30
|
$: $tiptap && $tiptap.setEditable(editable);
|
|
30
31
|
$: browser && (window.__image_uploader = imageUpload);
|
|
32
|
+
$: browser && (window.__tiptap_blocks = blocks);
|
|
31
33
|
if (browser) {
|
|
32
34
|
onMount(() => {
|
|
33
35
|
body = last = san(body);
|
|
@@ -64,14 +66,17 @@ $: selectedIndex = $slashVisible ? selectedIndex : 0;
|
|
|
64
66
|
function handleKeydown(event) {
|
|
65
67
|
if (!$slashVisible)
|
|
66
68
|
return;
|
|
69
|
+
let count = $slashItems.length;
|
|
70
|
+
if ($slashItems[0]?.list)
|
|
71
|
+
count = $slashItems.reduce((acc, item) => acc + item.list.length, 0);
|
|
67
72
|
if (event.key === 'ArrowUp') {
|
|
68
73
|
event.preventDefault();
|
|
69
|
-
selectedIndex = (selectedIndex +
|
|
74
|
+
selectedIndex = (selectedIndex + count - 1) % count;
|
|
70
75
|
return true;
|
|
71
76
|
}
|
|
72
77
|
if (event.key === 'ArrowDown') {
|
|
73
78
|
event.preventDefault();
|
|
74
|
-
selectedIndex = (selectedIndex + 1) %
|
|
79
|
+
selectedIndex = (selectedIndex + 1) % count;
|
|
75
80
|
return true;
|
|
76
81
|
}
|
|
77
82
|
if (event.key === 'Enter') {
|
|
@@ -82,10 +87,10 @@ function handleKeydown(event) {
|
|
|
82
87
|
return false;
|
|
83
88
|
}
|
|
84
89
|
function selectItem(index) {
|
|
85
|
-
const item = $slashItems[index];
|
|
90
|
+
const item = $slashItems[0]?.list ? $slashItems.map(i => i.list).flat()[index] : $slashItems[index];
|
|
86
91
|
if (item) {
|
|
87
92
|
let range = $slashProps.range;
|
|
88
|
-
item.command({ editor:
|
|
93
|
+
item.command({ editor: $tiptap, range });
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
</script>
|
|
@@ -8,7 +8,8 @@ declare const __propDef: {
|
|
|
8
8
|
ref?: null | undefined;
|
|
9
9
|
options?: {} | undefined;
|
|
10
10
|
imageUpload?: UploadFn | undefined;
|
|
11
|
-
style?:
|
|
11
|
+
style?: string | undefined;
|
|
12
|
+
blocks?: any[] | undefined;
|
|
12
13
|
};
|
|
13
14
|
events: {
|
|
14
15
|
[evt: string]: CustomEvent<any>;
|
|
@@ -21,7 +22,5 @@ export type TipTapProps = typeof __propDef.props;
|
|
|
21
22
|
export type TipTapEvents = typeof __propDef.events;
|
|
22
23
|
export type TipTapSlots = typeof __propDef.slots;
|
|
23
24
|
export default class TipTap extends SvelteComponentTyped<TipTapProps, TipTapEvents, TipTapSlots> {
|
|
24
|
-
get imageUpload(): UploadFn;
|
|
25
|
-
get style(): "";
|
|
26
25
|
}
|
|
27
26
|
export {};
|
package/dist/tiptap/tiptap.js
CHANGED
|
@@ -23,55 +23,60 @@ import Iframe from "../plugin/iframe";
|
|
|
23
23
|
// @ts-ignore
|
|
24
24
|
import { MathInline, MathBlock } from "@seorii/prosemirror-math/tiptap";
|
|
25
25
|
import Youtube from "../plugin/youtube";
|
|
26
|
-
import command from "../plugin/command";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
import command from "../plugin/command/suggest";
|
|
27
|
+
import emoji from "../plugin/command/emoji";
|
|
28
|
+
export default (element, content, { plugins = [], ...props } = {}) => {
|
|
29
|
+
const tt = new Editor({
|
|
30
|
+
element, content, ...props,
|
|
31
|
+
extensions: [
|
|
32
|
+
CodeBlockLowlight.extend({
|
|
33
|
+
addKeyboardShortcuts() {
|
|
34
|
+
return {
|
|
35
|
+
...this.parent?.(),
|
|
36
|
+
'Tab': () => {
|
|
37
|
+
if (this.editor.isActive('codeBlock')) {
|
|
38
|
+
return this.editor.commands.insertContent(' ');
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}).configure({ lowlight }),
|
|
45
|
+
Image,
|
|
46
|
+
Youtube,
|
|
47
|
+
StarterKit,
|
|
48
|
+
Underline,
|
|
49
|
+
Highlight.configure({ multicolor: true }),
|
|
50
|
+
Link.configure({
|
|
51
|
+
openOnClick: true, protocols: ['ftp', 'mailto', {
|
|
52
|
+
scheme: 'tel',
|
|
53
|
+
optionalSlashes: true
|
|
54
|
+
}]
|
|
55
|
+
}),
|
|
56
|
+
TextAlign.configure({ types: ['heading', 'paragraph', 'image'] }),
|
|
57
|
+
DropCursor,
|
|
58
|
+
orderedlist,
|
|
59
|
+
MathInline,
|
|
60
|
+
MathBlock,
|
|
61
|
+
table,
|
|
62
|
+
tableHeader,
|
|
63
|
+
tableRow,
|
|
64
|
+
tableCell,
|
|
65
|
+
Superscript,
|
|
66
|
+
Subscript,
|
|
67
|
+
Indent,
|
|
68
|
+
Color,
|
|
69
|
+
TextStyle,
|
|
70
|
+
Iframe,
|
|
71
|
+
Code.extend({
|
|
72
|
+
renderHTML({ HTMLAttributes }) {
|
|
73
|
+
return ['code', mergeAttributes(HTMLAttributes, { class: 'inline' })];
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
...plugins,
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
tt.registerPlugin(emoji(tt));
|
|
80
|
+
tt.registerPlugin(command(tt));
|
|
81
|
+
return tt;
|
|
82
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seorii/tiptap",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "svelte-kit sync && svelte-package",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"@tiptap/pm": "^2.0.4",
|
|
61
61
|
"@tiptap/starter-kit": "^2.0.4",
|
|
62
62
|
"@tiptap/suggestion": "^2.0.4",
|
|
63
|
+
"emojis-keywords": "2.0.0",
|
|
64
|
+
"emojis-list": "2.0.0",
|
|
63
65
|
"lowlight": "^2.9.0",
|
|
64
66
|
"nunui": "^0.0.101",
|
|
65
67
|
"prosemirror-commands": "^1.5.2",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Extension } from '@tiptap/core';
|
|
2
|
-
import Suggestion from '@tiptap/suggestion';
|
|
3
|
-
import suggestion from "./suggest";
|
|
4
|
-
export const Command = Extension.create({
|
|
5
|
-
name: 'slash',
|
|
6
|
-
addOptions() {
|
|
7
|
-
return {
|
|
8
|
-
suggestion: {
|
|
9
|
-
char: '/',
|
|
10
|
-
command: ({ editor, range, props }) => {
|
|
11
|
-
props.command({ editor, range });
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
},
|
|
16
|
-
addProseMirrorPlugins() {
|
|
17
|
-
return [
|
|
18
|
-
Suggestion({
|
|
19
|
-
editor: this.editor,
|
|
20
|
-
...this.options.suggestion
|
|
21
|
-
})
|
|
22
|
-
];
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
export default Command.configure({
|
|
26
|
-
suggestion
|
|
27
|
-
});
|