frappe-ui 0.0.67 → 0.0.69
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/package.json +2 -1
- package/src/components/Dropdown.vue +2 -3
- package/src/components/TextEditor/Menu.vue +2 -1
- package/src/components/TextEditor/TextEditor.vue +27 -171
- package/src/components/TextEditor/TextEditorBubbleMenu.vue +68 -0
- package/src/components/TextEditor/TextEditorFixedMenu.vue +70 -0
- package/src/components/TextEditor/TextEditorFloatingMenu.vue +55 -0
- package/src/components/TextEditor/utils.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.69",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@tiptap/extension-table-row": "^2.0.0-beta.22",
|
|
34
34
|
"@tiptap/extension-text-align": "^2.0.0-beta.31",
|
|
35
35
|
"@tiptap/starter-kit": "^2.0.0-beta.191",
|
|
36
|
+
"@tiptap/suggestion": "^2.0.0-beta.195",
|
|
36
37
|
"@tiptap/vue-3": "^2.0.0-beta.96",
|
|
37
38
|
"autoprefixer": "^10.4.2",
|
|
38
39
|
"feather-icons": "^4.28.0",
|
|
@@ -43,8 +43,7 @@
|
|
|
43
43
|
@click="item.onClick"
|
|
44
44
|
>
|
|
45
45
|
<component :is="item.component" v-if="item.component" />
|
|
46
|
-
|
|
47
|
-
<span v-else>
|
|
46
|
+
<template v-else>
|
|
48
47
|
<FeatherIcon
|
|
49
48
|
v-if="item.icon"
|
|
50
49
|
:name="item.icon"
|
|
@@ -54,7 +53,7 @@
|
|
|
54
53
|
<span class="whitespace-nowrap">
|
|
55
54
|
{{ item.label }}
|
|
56
55
|
</span>
|
|
57
|
-
</
|
|
56
|
+
</template>
|
|
58
57
|
</button>
|
|
59
58
|
</MenuItem>
|
|
60
59
|
</div>
|
|
@@ -88,7 +88,8 @@ import { Popover, Dialog, Input, Button } from 'frappe-ui'
|
|
|
88
88
|
import InsertImage from './InsertImage.vue'
|
|
89
89
|
export default {
|
|
90
90
|
name: 'TipTapMenu',
|
|
91
|
-
props: ['
|
|
91
|
+
props: ['buttons'],
|
|
92
|
+
inject: ['editor'],
|
|
92
93
|
components: {
|
|
93
94
|
Popover,
|
|
94
95
|
Dialog,
|
|
@@ -1,51 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative w-full" :class="$attrs.class" v-if="editor">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
class="bubble-menu rounded-md shadow-sm"
|
|
6
|
-
:tippy-options="{ duration: 100 }"
|
|
7
|
-
:editor="editor"
|
|
8
|
-
>
|
|
9
|
-
<Menu
|
|
10
|
-
:editor="editor"
|
|
11
|
-
class="rounded-md border border-gray-100 shadow-lg"
|
|
12
|
-
:buttons="bubbleMenuButtons"
|
|
13
|
-
/>
|
|
14
|
-
</BubbleMenu>
|
|
15
|
-
|
|
16
|
-
<Menu
|
|
17
|
-
v-if="fixedMenuButtons"
|
|
3
|
+
<TextEditorBubbleMenu :buttons="bubbleMenu" />
|
|
4
|
+
<TextEditorFixedMenu
|
|
18
5
|
class="w-full overflow-x-auto rounded-t-lg border border-gray-200"
|
|
19
|
-
:
|
|
20
|
-
:buttons="fixedMenuButtons"
|
|
6
|
+
:buttons="fixedMenu"
|
|
21
7
|
/>
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
v-if="floatingMenuButtons"
|
|
25
|
-
:tippy-options="{ duration: 100 }"
|
|
26
|
-
:editor="editor"
|
|
27
|
-
class="flex"
|
|
28
|
-
>
|
|
29
|
-
<button
|
|
30
|
-
v-for="button in floatingMenuButtons"
|
|
31
|
-
:key="button.label"
|
|
32
|
-
class="flex rounded p-1 text-gray-800 transition-colors"
|
|
33
|
-
:class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
|
|
34
|
-
@click="() => button.action(editor)"
|
|
35
|
-
:title="button.label"
|
|
36
|
-
>
|
|
37
|
-
<component v-if="button.icon" :is="button.icon" class="h-4 w-4" />
|
|
38
|
-
<span class="inline-block h-4 min-w-[1rem] text-sm leading-4" v-else>
|
|
39
|
-
{{ button.text }}
|
|
40
|
-
</span>
|
|
41
|
-
</button>
|
|
42
|
-
</FloatingMenu>
|
|
8
|
+
<TextEditorFloatingMenu :buttons="floatingMenu" />
|
|
9
|
+
<slot name="top" />
|
|
43
10
|
<editor-content :editor="editor" />
|
|
11
|
+
<slot name="bottom" />
|
|
44
12
|
</div>
|
|
45
13
|
</template>
|
|
46
14
|
|
|
47
15
|
<script>
|
|
48
|
-
import {
|
|
16
|
+
import { normalizeClass } from 'vue'
|
|
17
|
+
import { computed } from '@vue/reactivity'
|
|
18
|
+
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
49
19
|
import StarterKit from '@tiptap/starter-kit'
|
|
50
20
|
import Placeholder from '@tiptap/extension-placeholder'
|
|
51
21
|
import TextAlign from '@tiptap/extension-text-align'
|
|
@@ -56,18 +26,18 @@ import TableRow from '@tiptap/extension-table-row'
|
|
|
56
26
|
import Image from './image-extension'
|
|
57
27
|
import Link from '@tiptap/extension-link'
|
|
58
28
|
import configureMention from './mention'
|
|
59
|
-
import
|
|
60
|
-
import
|
|
61
|
-
import
|
|
29
|
+
import TextEditorFixedMenu from './TextEditorFixedMenu.vue'
|
|
30
|
+
import TextEditorBubbleMenu from './TextEditorBubbleMenu.vue'
|
|
31
|
+
import TextEditorFloatingMenu from './TextEditorFloatingMenu.vue'
|
|
62
32
|
|
|
63
33
|
export default {
|
|
64
34
|
name: 'TextEditor',
|
|
65
35
|
inheritAttrs: false,
|
|
66
36
|
components: {
|
|
67
37
|
EditorContent,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
38
|
+
TextEditorFixedMenu,
|
|
39
|
+
TextEditorBubbleMenu,
|
|
40
|
+
TextEditorFloatingMenu,
|
|
71
41
|
},
|
|
72
42
|
props: {
|
|
73
43
|
content: {
|
|
@@ -113,6 +83,11 @@ export default {
|
|
|
113
83
|
},
|
|
114
84
|
emits: ['change'],
|
|
115
85
|
expose: ['editor'],
|
|
86
|
+
provide() {
|
|
87
|
+
return {
|
|
88
|
+
editor: computed(() => this.editor),
|
|
89
|
+
}
|
|
90
|
+
},
|
|
116
91
|
data() {
|
|
117
92
|
return {
|
|
118
93
|
editor: null,
|
|
@@ -175,124 +150,9 @@ export default {
|
|
|
175
150
|
},
|
|
176
151
|
beforeUnmount() {
|
|
177
152
|
this.editor.destroy()
|
|
153
|
+
this.editor = null
|
|
178
154
|
},
|
|
179
155
|
computed: {
|
|
180
|
-
fixedMenuButtons() {
|
|
181
|
-
if (!this.fixedMenu) return false
|
|
182
|
-
|
|
183
|
-
let buttons
|
|
184
|
-
if (Array.isArray(this.fixedMenu)) {
|
|
185
|
-
buttons = this.fixedMenu
|
|
186
|
-
} else {
|
|
187
|
-
buttons = [
|
|
188
|
-
[
|
|
189
|
-
'Heading 1',
|
|
190
|
-
'Heading 2',
|
|
191
|
-
'Heading 3',
|
|
192
|
-
'Heading 4',
|
|
193
|
-
'Heading 5',
|
|
194
|
-
'Heading 6',
|
|
195
|
-
],
|
|
196
|
-
'Paragraph',
|
|
197
|
-
'Separator',
|
|
198
|
-
'Bold',
|
|
199
|
-
'Italic',
|
|
200
|
-
'Separator',
|
|
201
|
-
'Bullet List',
|
|
202
|
-
'Numbered List',
|
|
203
|
-
'Separator',
|
|
204
|
-
'Align Left',
|
|
205
|
-
'Align Center',
|
|
206
|
-
'Align Right',
|
|
207
|
-
'Separator',
|
|
208
|
-
'Image',
|
|
209
|
-
'Link',
|
|
210
|
-
'Blockquote',
|
|
211
|
-
'Code',
|
|
212
|
-
'Horizontal Rule',
|
|
213
|
-
[
|
|
214
|
-
'InsertTable',
|
|
215
|
-
'AddColumnBefore',
|
|
216
|
-
'AddColumnAfter',
|
|
217
|
-
'DeleteColumn',
|
|
218
|
-
'AddRowBefore',
|
|
219
|
-
'AddRowAfter',
|
|
220
|
-
'DeleteRow',
|
|
221
|
-
'MergeCells',
|
|
222
|
-
'SplitCell',
|
|
223
|
-
'ToggleHeaderColumn',
|
|
224
|
-
'ToggleHeaderRow',
|
|
225
|
-
'ToggleHeaderCell',
|
|
226
|
-
'DeleteTable',
|
|
227
|
-
],
|
|
228
|
-
'Separator',
|
|
229
|
-
'Undo',
|
|
230
|
-
'Redo',
|
|
231
|
-
]
|
|
232
|
-
}
|
|
233
|
-
return buttons.map(createEditorButton)
|
|
234
|
-
},
|
|
235
|
-
bubbleMenuButtons() {
|
|
236
|
-
if (!this.bubbleMenu) return false
|
|
237
|
-
|
|
238
|
-
let buttons
|
|
239
|
-
if (Array.isArray(this.bubbleMenu)) {
|
|
240
|
-
buttons = this.bubbleMenu
|
|
241
|
-
} else {
|
|
242
|
-
buttons = [
|
|
243
|
-
'Paragraph',
|
|
244
|
-
'Heading 2',
|
|
245
|
-
'Heading 3',
|
|
246
|
-
'Separator',
|
|
247
|
-
'Bold',
|
|
248
|
-
'Italic',
|
|
249
|
-
'Link',
|
|
250
|
-
'Separator',
|
|
251
|
-
'Bullet List',
|
|
252
|
-
'Numbered List',
|
|
253
|
-
'Separator',
|
|
254
|
-
'Image',
|
|
255
|
-
'Blockquote',
|
|
256
|
-
'Code',
|
|
257
|
-
[
|
|
258
|
-
'InsertTable',
|
|
259
|
-
'AddColumnBefore',
|
|
260
|
-
'AddColumnAfter',
|
|
261
|
-
'DeleteColumn',
|
|
262
|
-
'AddRowBefore',
|
|
263
|
-
'AddRowAfter',
|
|
264
|
-
'DeleteRow',
|
|
265
|
-
'MergeCells',
|
|
266
|
-
'SplitCell',
|
|
267
|
-
'ToggleHeaderColumn',
|
|
268
|
-
'ToggleHeaderRow',
|
|
269
|
-
'ToggleHeaderCell',
|
|
270
|
-
'DeleteTable',
|
|
271
|
-
],
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
return buttons.map(createEditorButton)
|
|
275
|
-
},
|
|
276
|
-
floatingMenuButtons() {
|
|
277
|
-
if (!this.floatingMenu) return false
|
|
278
|
-
|
|
279
|
-
let buttons
|
|
280
|
-
if (Array.isArray(this.floatingMenu)) {
|
|
281
|
-
buttons = this.floatingMenu
|
|
282
|
-
} else {
|
|
283
|
-
buttons = [
|
|
284
|
-
'Paragraph',
|
|
285
|
-
'Heading 2',
|
|
286
|
-
'Heading 3',
|
|
287
|
-
'Bullet List',
|
|
288
|
-
'Numbered List',
|
|
289
|
-
'Blockquote',
|
|
290
|
-
'Code',
|
|
291
|
-
'Horizontal Rule',
|
|
292
|
-
]
|
|
293
|
-
}
|
|
294
|
-
return buttons.map(createEditorButton)
|
|
295
|
-
},
|
|
296
156
|
editorProps() {
|
|
297
157
|
return {
|
|
298
158
|
attributes: {
|
|
@@ -305,18 +165,14 @@ export default {
|
|
|
305
165
|
},
|
|
306
166
|
},
|
|
307
167
|
}
|
|
308
|
-
|
|
309
|
-
function createEditorButton(option) {
|
|
310
|
-
if (option instanceof Array) {
|
|
311
|
-
return option.map(createEditorButton)
|
|
312
|
-
}
|
|
313
|
-
if (typeof option == 'object') {
|
|
314
|
-
return option
|
|
315
|
-
}
|
|
316
|
-
return commands[option]
|
|
317
|
-
}
|
|
318
168
|
</script>
|
|
319
169
|
<style>
|
|
170
|
+
.ProseMirror {
|
|
171
|
+
outline: none;
|
|
172
|
+
caret-color: theme('colors.blue.600');
|
|
173
|
+
word-break: break-word;
|
|
174
|
+
}
|
|
175
|
+
|
|
320
176
|
/* Placeholder */
|
|
321
177
|
.ProseMirror:not(.ProseMirror-focused) p.is-editor-empty:first-child::before {
|
|
322
178
|
content: attr(data-placeholder);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BubbleMenu
|
|
3
|
+
v-if="bubbleMenuButtons"
|
|
4
|
+
class="bubble-menu rounded-md shadow-sm"
|
|
5
|
+
:tippy-options="{ duration: 100 }"
|
|
6
|
+
:editor="editor"
|
|
7
|
+
>
|
|
8
|
+
<Menu
|
|
9
|
+
class="rounded-md border border-gray-100 shadow-lg"
|
|
10
|
+
:buttons="bubbleMenuButtons"
|
|
11
|
+
/>
|
|
12
|
+
</BubbleMenu>
|
|
13
|
+
</template>
|
|
14
|
+
<script>
|
|
15
|
+
import { BubbleMenu } from '@tiptap/vue-3'
|
|
16
|
+
import { createEditorButton } from './utils'
|
|
17
|
+
import Menu from './Menu.vue'
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'TextEditorBubbleMenu',
|
|
21
|
+
props: ['buttons'],
|
|
22
|
+
components: { BubbleMenu, Menu },
|
|
23
|
+
inject: ['editor'],
|
|
24
|
+
computed: {
|
|
25
|
+
bubbleMenuButtons() {
|
|
26
|
+
if (!this.buttons) return false
|
|
27
|
+
|
|
28
|
+
let buttons
|
|
29
|
+
if (Array.isArray(this.buttons)) {
|
|
30
|
+
buttons = this.buttons
|
|
31
|
+
} else {
|
|
32
|
+
buttons = [
|
|
33
|
+
'Paragraph',
|
|
34
|
+
'Heading 2',
|
|
35
|
+
'Heading 3',
|
|
36
|
+
'Separator',
|
|
37
|
+
'Bold',
|
|
38
|
+
'Italic',
|
|
39
|
+
'Link',
|
|
40
|
+
'Separator',
|
|
41
|
+
'Bullet List',
|
|
42
|
+
'Numbered List',
|
|
43
|
+
'Separator',
|
|
44
|
+
'Image',
|
|
45
|
+
'Blockquote',
|
|
46
|
+
'Code',
|
|
47
|
+
[
|
|
48
|
+
'InsertTable',
|
|
49
|
+
'AddColumnBefore',
|
|
50
|
+
'AddColumnAfter',
|
|
51
|
+
'DeleteColumn',
|
|
52
|
+
'AddRowBefore',
|
|
53
|
+
'AddRowAfter',
|
|
54
|
+
'DeleteRow',
|
|
55
|
+
'MergeCells',
|
|
56
|
+
'SplitCell',
|
|
57
|
+
'ToggleHeaderColumn',
|
|
58
|
+
'ToggleHeaderRow',
|
|
59
|
+
'ToggleHeaderCell',
|
|
60
|
+
'DeleteTable',
|
|
61
|
+
],
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
return buttons.map(createEditorButton)
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Menu v-if="fixedMenuButtons" :buttons="fixedMenuButtons" />
|
|
3
|
+
</template>
|
|
4
|
+
<script>
|
|
5
|
+
import Menu from './Menu.vue'
|
|
6
|
+
import { createEditorButton } from './utils'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'TextEditorFixedMenu',
|
|
10
|
+
props: ['buttons'],
|
|
11
|
+
components: { Menu },
|
|
12
|
+
inject: ['editor'],
|
|
13
|
+
computed: {
|
|
14
|
+
fixedMenuButtons() {
|
|
15
|
+
if (!this.buttons) return false
|
|
16
|
+
let buttons
|
|
17
|
+
if (Array.isArray(this.buttons)) {
|
|
18
|
+
buttons = this.buttons
|
|
19
|
+
} else {
|
|
20
|
+
buttons = [
|
|
21
|
+
[
|
|
22
|
+
'Heading 1',
|
|
23
|
+
'Heading 2',
|
|
24
|
+
'Heading 3',
|
|
25
|
+
'Heading 4',
|
|
26
|
+
'Heading 5',
|
|
27
|
+
'Heading 6',
|
|
28
|
+
],
|
|
29
|
+
'Paragraph',
|
|
30
|
+
'Separator',
|
|
31
|
+
'Bold',
|
|
32
|
+
'Italic',
|
|
33
|
+
'Separator',
|
|
34
|
+
'Bullet List',
|
|
35
|
+
'Numbered List',
|
|
36
|
+
'Separator',
|
|
37
|
+
'Align Left',
|
|
38
|
+
'Align Center',
|
|
39
|
+
'Align Right',
|
|
40
|
+
'Separator',
|
|
41
|
+
'Image',
|
|
42
|
+
'Link',
|
|
43
|
+
'Blockquote',
|
|
44
|
+
'Code',
|
|
45
|
+
'Horizontal Rule',
|
|
46
|
+
[
|
|
47
|
+
'InsertTable',
|
|
48
|
+
'AddColumnBefore',
|
|
49
|
+
'AddColumnAfter',
|
|
50
|
+
'DeleteColumn',
|
|
51
|
+
'AddRowBefore',
|
|
52
|
+
'AddRowAfter',
|
|
53
|
+
'DeleteRow',
|
|
54
|
+
'MergeCells',
|
|
55
|
+
'SplitCell',
|
|
56
|
+
'ToggleHeaderColumn',
|
|
57
|
+
'ToggleHeaderRow',
|
|
58
|
+
'ToggleHeaderCell',
|
|
59
|
+
'DeleteTable',
|
|
60
|
+
],
|
|
61
|
+
'Separator',
|
|
62
|
+
'Undo',
|
|
63
|
+
'Redo',
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
return buttons.map(createEditorButton)
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FloatingMenu
|
|
3
|
+
v-if="floatingMenuButtons"
|
|
4
|
+
:tippy-options="{ duration: 100 }"
|
|
5
|
+
:editor="editor"
|
|
6
|
+
class="flex"
|
|
7
|
+
>
|
|
8
|
+
<button
|
|
9
|
+
v-for="button in floatingMenuButtons"
|
|
10
|
+
:key="button.label"
|
|
11
|
+
class="flex rounded p-1 text-gray-800 transition-colors"
|
|
12
|
+
:class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
|
|
13
|
+
@click="() => button.action(editor)"
|
|
14
|
+
:title="button.label"
|
|
15
|
+
>
|
|
16
|
+
<component v-if="button.icon" :is="button.icon" class="h-4 w-4" />
|
|
17
|
+
<span class="inline-block h-4 min-w-[1rem] text-sm leading-4" v-else>
|
|
18
|
+
{{ button.text }}
|
|
19
|
+
</span>
|
|
20
|
+
</button>
|
|
21
|
+
</FloatingMenu>
|
|
22
|
+
</template>
|
|
23
|
+
<script>
|
|
24
|
+
import { FloatingMenu } from '@tiptap/vue-3'
|
|
25
|
+
import { createEditorButton } from './utils'
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
name: 'TextEditorFloatingMenu',
|
|
29
|
+
props: ['buttons'],
|
|
30
|
+
components: { FloatingMenu },
|
|
31
|
+
inject: ['editor'],
|
|
32
|
+
computed: {
|
|
33
|
+
floatingMenuButtons() {
|
|
34
|
+
if (!this.buttons) return false
|
|
35
|
+
|
|
36
|
+
let buttons
|
|
37
|
+
if (Array.isArray(this.buttons)) {
|
|
38
|
+
buttons = this.buttons
|
|
39
|
+
} else {
|
|
40
|
+
buttons = [
|
|
41
|
+
'Paragraph',
|
|
42
|
+
'Heading 2',
|
|
43
|
+
'Heading 3',
|
|
44
|
+
'Bullet List',
|
|
45
|
+
'Numbered List',
|
|
46
|
+
'Blockquote',
|
|
47
|
+
'Code',
|
|
48
|
+
'Horizontal Rule',
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
return buttons.map(createEditorButton)
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
</script>
|