frappe-ui 0.0.13 → 0.0.15
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"author": "Frappe Technologies Pvt. Ltd.",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@headlessui/vue": "^1.
|
|
20
|
+
"@headlessui/vue": "^1.5.0",
|
|
21
21
|
"@popperjs/core": "^2.11.2",
|
|
22
22
|
"@tailwindcss/forms": "^0.4.0",
|
|
23
23
|
"@tailwindcss/typography": "^0.5.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="inline-flex px-1 py-1 bg-white">
|
|
3
3
|
<div class="inline-flex items-center gap-1">
|
|
4
|
-
<template v-for="button in
|
|
4
|
+
<template v-for="button in buttons" :key="button.label">
|
|
5
5
|
<div
|
|
6
6
|
class="border-l w-[2px] h-4"
|
|
7
7
|
v-if="button.type === 'separator'"
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<button
|
|
10
10
|
v-else
|
|
11
11
|
class="flex p-1 text-gray-800 transition-colors rounded"
|
|
12
|
-
:class="button.isActive() ? 'bg-gray-100' : 'hover:bg-gray-100'"
|
|
13
|
-
@click="button.action"
|
|
12
|
+
:class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
|
|
13
|
+
@click="() => button.action(editor)"
|
|
14
14
|
:title="button.label"
|
|
15
15
|
>
|
|
16
16
|
<FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
|
|
@@ -26,96 +26,7 @@
|
|
|
26
26
|
import { FeatherIcon } from 'frappe-ui'
|
|
27
27
|
export default {
|
|
28
28
|
name: 'TipTapMenu',
|
|
29
|
-
props: ['editor'],
|
|
29
|
+
props: ['editor', 'buttons'],
|
|
30
30
|
components: { FeatherIcon },
|
|
31
|
-
computed: {
|
|
32
|
-
menuButtons() {
|
|
33
|
-
return [
|
|
34
|
-
{
|
|
35
|
-
label: 'Paragraph',
|
|
36
|
-
icon: 'type',
|
|
37
|
-
action: () => this.editor.chain().focus().setParagraph().run(),
|
|
38
|
-
isActive: () => this.editor.isActive('paragraph'),
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
label: 'Heading 2',
|
|
42
|
-
text: 'H2',
|
|
43
|
-
action: () =>
|
|
44
|
-
this.editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
45
|
-
isActive: () => this.editor.isActive('heading', { level: 2 }),
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
label: 'Heading 3',
|
|
49
|
-
text: 'H3',
|
|
50
|
-
action: () =>
|
|
51
|
-
this.editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
52
|
-
isActive: () => this.editor.isActive('heading', { level: 3 }),
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
type: 'separator',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
label: 'Bold',
|
|
59
|
-
icon: 'bold',
|
|
60
|
-
action: () => this.editor.chain().focus().toggleBold().run(),
|
|
61
|
-
isActive: () => this.editor.isActive('bold'),
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
label: 'Italic',
|
|
65
|
-
icon: 'italic',
|
|
66
|
-
action: () => this.editor.chain().focus().toggleItalic().run(),
|
|
67
|
-
isActive: () => this.editor.isActive('italic'),
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
type: 'separator',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
label: 'Bullet List',
|
|
74
|
-
icon: 'list',
|
|
75
|
-
action: () => this.editor.chain().focus().toggleBulletList().run(),
|
|
76
|
-
isActive: () => this.editor.isActive('bulletList'),
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
label: 'Numbered List',
|
|
80
|
-
text: '1.',
|
|
81
|
-
action: () => this.editor.chain().focus().toggleOrderedList().run(),
|
|
82
|
-
isActive: () => this.editor.isActive('orderedList'),
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
label: 'Blockquote',
|
|
86
|
-
icon: 'chevron-right',
|
|
87
|
-
action: () => this.editor.chain().focus().toggleBlockquote().run(),
|
|
88
|
-
isActive: () => this.editor.isActive('blockquote'),
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
label: 'Code',
|
|
92
|
-
icon: 'code',
|
|
93
|
-
action: () => this.editor.chain().focus().toggleCodeBlock().run(),
|
|
94
|
-
isActive: () => this.editor.isActive('codeBlock'),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
label: 'Horizontal Rule',
|
|
98
|
-
icon: 'minus',
|
|
99
|
-
action: () => this.editor.chain().focus().setHorizontalRule().run(),
|
|
100
|
-
isActive: () => false,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
type: 'separator',
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
label: 'Undo',
|
|
107
|
-
icon: 'corner-up-left',
|
|
108
|
-
action: () => this.editor.chain().focus().undo().run(),
|
|
109
|
-
isActive: () => false,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
label: 'Redo',
|
|
113
|
-
icon: 'corner-up-right',
|
|
114
|
-
action: () => this.editor.chain().focus().redo().run(),
|
|
115
|
-
isActive: () => false,
|
|
116
|
-
},
|
|
117
|
-
]
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
31
|
}
|
|
121
32
|
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="relative w-full" :class="
|
|
2
|
+
<div class="relative w-full" :class="$attrs.class" v-if="editor">
|
|
3
3
|
<BubbleMenu
|
|
4
|
-
v-if="
|
|
4
|
+
v-if="bubbleMenuButtons"
|
|
5
5
|
class="bubble-menu"
|
|
6
6
|
:tippy-options="{ duration: 100 }"
|
|
7
7
|
:editor="editor"
|
|
@@ -9,27 +9,48 @@
|
|
|
9
9
|
<Menu
|
|
10
10
|
:editor="editor"
|
|
11
11
|
class="border border-gray-100 rounded-md shadow-sm"
|
|
12
|
+
:buttons="bubbleMenuButtons"
|
|
12
13
|
/>
|
|
13
14
|
</BubbleMenu>
|
|
14
15
|
|
|
15
16
|
<Menu
|
|
16
|
-
v-if="
|
|
17
|
-
class="
|
|
17
|
+
v-if="fixedMenuButtons"
|
|
18
|
+
class="w-full border rounded-t-lg border-gray-50 border-b-gray-100"
|
|
18
19
|
:editor="editor"
|
|
20
|
+
:buttons="fixedMenuButtons"
|
|
19
21
|
/>
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
<FloatingMenu
|
|
24
|
+
v-if="floatingMenuButtons"
|
|
25
|
+
:tippy-options="{ duration: 100 }"
|
|
21
26
|
:editor="editor"
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
class="flex"
|
|
28
|
+
>
|
|
29
|
+
<button
|
|
30
|
+
v-for="button in floatingMenuButtons"
|
|
31
|
+
:key="button.label"
|
|
32
|
+
class="flex p-1 text-gray-800 transition-colors rounded"
|
|
33
|
+
:class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
|
|
34
|
+
@click="() => button.action(editor)"
|
|
35
|
+
:title="button.label"
|
|
36
|
+
>
|
|
37
|
+
<FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
|
|
38
|
+
<span class="inline-block h-4 text-sm leading-4 min-w-[1rem]" v-else>
|
|
39
|
+
{{ button.text }}
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
</FloatingMenu>
|
|
43
|
+
<editor-content :editor="editor" />
|
|
24
44
|
</div>
|
|
25
45
|
</template>
|
|
26
46
|
|
|
27
47
|
<script>
|
|
28
|
-
import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
|
|
48
|
+
import { Editor, EditorContent, BubbleMenu, FloatingMenu } from '@tiptap/vue-3'
|
|
29
49
|
import StarterKit from '@tiptap/starter-kit'
|
|
30
50
|
import Placeholder from '@tiptap/extension-placeholder'
|
|
31
51
|
import Image from '@tiptap/extension-image'
|
|
32
52
|
import Menu from './Menu.vue'
|
|
53
|
+
import commands from './commands'
|
|
33
54
|
|
|
34
55
|
export default {
|
|
35
56
|
name: 'TextEditor',
|
|
@@ -37,14 +58,17 @@ export default {
|
|
|
37
58
|
components: {
|
|
38
59
|
EditorContent,
|
|
39
60
|
BubbleMenu,
|
|
61
|
+
FloatingMenu,
|
|
40
62
|
Menu,
|
|
41
63
|
},
|
|
42
64
|
props: [
|
|
43
65
|
'content',
|
|
44
66
|
'placeholder',
|
|
45
67
|
'editorClass',
|
|
46
|
-
'
|
|
47
|
-
'
|
|
68
|
+
'fixedMenu',
|
|
69
|
+
'bubbleMenu',
|
|
70
|
+
'floatingMenu',
|
|
71
|
+
'extensions',
|
|
48
72
|
],
|
|
49
73
|
emits: ['change'],
|
|
50
74
|
expose: ['editor'],
|
|
@@ -58,7 +82,7 @@ export default {
|
|
|
58
82
|
content: this.content || '<p></p>',
|
|
59
83
|
editorProps: {
|
|
60
84
|
attributes: {
|
|
61
|
-
class: ['prose-p:my-1', this.editorClass].join(' '),
|
|
85
|
+
class: ['prose prose-sm prose-p:my-1', this.editorClass].join(' '),
|
|
62
86
|
},
|
|
63
87
|
},
|
|
64
88
|
extensions: [
|
|
@@ -67,6 +91,7 @@ export default {
|
|
|
67
91
|
Placeholder.configure({
|
|
68
92
|
placeholder: this.placeholder || 'Write something...',
|
|
69
93
|
}),
|
|
94
|
+
...(this.extensions || []),
|
|
70
95
|
],
|
|
71
96
|
onUpdate: ({ editor }) => {
|
|
72
97
|
this.$emit('change', editor.getHTML())
|
|
@@ -76,6 +101,85 @@ export default {
|
|
|
76
101
|
beforeUnmount() {
|
|
77
102
|
this.editor.destroy()
|
|
78
103
|
},
|
|
104
|
+
computed: {
|
|
105
|
+
fixedMenuButtons() {
|
|
106
|
+
if (!this.fixedMenu) return false
|
|
107
|
+
|
|
108
|
+
let buttons
|
|
109
|
+
if (Array.isArray(this.fixedMenu)) {
|
|
110
|
+
buttons = this.fixedMenu
|
|
111
|
+
} else {
|
|
112
|
+
buttons = [
|
|
113
|
+
'Paragraph',
|
|
114
|
+
'Heading 2',
|
|
115
|
+
'Heading 3',
|
|
116
|
+
'Separator',
|
|
117
|
+
'Bold',
|
|
118
|
+
'Italic',
|
|
119
|
+
'Separator',
|
|
120
|
+
'Bullet List',
|
|
121
|
+
'Numbered List',
|
|
122
|
+
'Blockquote',
|
|
123
|
+
'Code',
|
|
124
|
+
'Horizontal Rule',
|
|
125
|
+
'Separator',
|
|
126
|
+
'Undo',
|
|
127
|
+
'Redo',
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
return buttons.map(createEditorButton)
|
|
131
|
+
},
|
|
132
|
+
bubbleMenuButtons() {
|
|
133
|
+
if (!this.bubbleMenu) return false
|
|
134
|
+
|
|
135
|
+
let buttons
|
|
136
|
+
if (Array.isArray(this.bubbleMenu)) {
|
|
137
|
+
buttons = this.bubbleMenu
|
|
138
|
+
} else {
|
|
139
|
+
buttons = [
|
|
140
|
+
'Paragraph',
|
|
141
|
+
'Heading 2',
|
|
142
|
+
'Heading 3',
|
|
143
|
+
'Separator',
|
|
144
|
+
'Bold',
|
|
145
|
+
'Italic',
|
|
146
|
+
'Separator',
|
|
147
|
+
'Bullet List',
|
|
148
|
+
'Numbered List',
|
|
149
|
+
'Blockquote',
|
|
150
|
+
'Code',
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
return buttons.map(createEditorButton)
|
|
154
|
+
},
|
|
155
|
+
floatingMenuButtons() {
|
|
156
|
+
if (!this.floatingMenu) return false
|
|
157
|
+
|
|
158
|
+
let buttons
|
|
159
|
+
if (Array.isArray(this.floatingMenu)) {
|
|
160
|
+
buttons = this.floatingMenu
|
|
161
|
+
} else {
|
|
162
|
+
buttons = [
|
|
163
|
+
'Paragraph',
|
|
164
|
+
'Heading 2',
|
|
165
|
+
'Heading 3',
|
|
166
|
+
'Bullet List',
|
|
167
|
+
'Numbered List',
|
|
168
|
+
'Blockquote',
|
|
169
|
+
'Code',
|
|
170
|
+
'Horizontal Rule',
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
return buttons.map(createEditorButton)
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createEditorButton(option) {
|
|
179
|
+
if (typeof option == 'object') {
|
|
180
|
+
return option
|
|
181
|
+
}
|
|
182
|
+
return commands[option]
|
|
79
183
|
}
|
|
80
184
|
</script>
|
|
81
185
|
<style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
Paragraph: {
|
|
3
|
+
label: 'Paragraph',
|
|
4
|
+
icon: 'type',
|
|
5
|
+
action: (editor) => editor.chain().focus().setParagraph().run(),
|
|
6
|
+
isActive: (editor) => editor.isActive('paragraph'),
|
|
7
|
+
},
|
|
8
|
+
'Heading 2': {
|
|
9
|
+
label: 'Heading 2',
|
|
10
|
+
text: 'H2',
|
|
11
|
+
action: (editor) =>
|
|
12
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
13
|
+
isActive: (editor) => editor.isActive('heading', { level: 2 }),
|
|
14
|
+
},
|
|
15
|
+
'Heading 3': {
|
|
16
|
+
label: 'Heading 3',
|
|
17
|
+
text: 'H3',
|
|
18
|
+
action: (editor) =>
|
|
19
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
20
|
+
isActive: (editor) => editor.isActive('heading', { level: 3 }),
|
|
21
|
+
},
|
|
22
|
+
Bold: {
|
|
23
|
+
label: 'Bold',
|
|
24
|
+
icon: 'bold',
|
|
25
|
+
action: (editor) => editor.chain().focus().toggleBold().run(),
|
|
26
|
+
isActive: (editor) => editor.isActive('bold'),
|
|
27
|
+
},
|
|
28
|
+
Italic: {
|
|
29
|
+
label: 'Italic',
|
|
30
|
+
icon: 'italic',
|
|
31
|
+
action: (editor) => editor.chain().focus().toggleItalic().run(),
|
|
32
|
+
isActive: (editor) => editor.isActive('italic'),
|
|
33
|
+
},
|
|
34
|
+
'Bullet List': {
|
|
35
|
+
label: 'Bullet List',
|
|
36
|
+
icon: 'list',
|
|
37
|
+
action: (editor) => editor.chain().focus().toggleBulletList().run(),
|
|
38
|
+
isActive: (editor) => editor.isActive('bulletList'),
|
|
39
|
+
},
|
|
40
|
+
'Numbered List': {
|
|
41
|
+
label: 'Numbered List',
|
|
42
|
+
text: '1.',
|
|
43
|
+
action: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
|
44
|
+
isActive: (editor) => editor.isActive('orderedList'),
|
|
45
|
+
},
|
|
46
|
+
Blockquote: {
|
|
47
|
+
label: 'Blockquote',
|
|
48
|
+
icon: 'chevron-right',
|
|
49
|
+
action: (editor) => editor.chain().focus().toggleBlockquote().run(),
|
|
50
|
+
isActive: (editor) => editor.isActive('blockquote'),
|
|
51
|
+
},
|
|
52
|
+
Code: {
|
|
53
|
+
label: 'Code',
|
|
54
|
+
icon: 'code',
|
|
55
|
+
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
|
|
56
|
+
isActive: (editor) => editor.isActive('codeBlock'),
|
|
57
|
+
},
|
|
58
|
+
'Horizontal Rule': {
|
|
59
|
+
label: 'Horizontal Rule',
|
|
60
|
+
icon: 'minus',
|
|
61
|
+
action: (editor) => editor.chain().focus().setHorizontalRule().run(),
|
|
62
|
+
isActive: (editor) => false,
|
|
63
|
+
},
|
|
64
|
+
Undo: {
|
|
65
|
+
label: 'Undo',
|
|
66
|
+
icon: 'corner-up-left',
|
|
67
|
+
action: (editor) => editor.chain().focus().undo().run(),
|
|
68
|
+
isActive: (editor) => false,
|
|
69
|
+
},
|
|
70
|
+
Redo: {
|
|
71
|
+
label: 'Redo',
|
|
72
|
+
icon: 'corner-up-right',
|
|
73
|
+
action: (editor) => editor.chain().focus().redo().run(),
|
|
74
|
+
isActive: (editor) => false,
|
|
75
|
+
},
|
|
76
|
+
Separator: {
|
|
77
|
+
type: 'separator',
|
|
78
|
+
},
|
|
79
|
+
}
|
package/src/utils/resources.js
CHANGED
|
@@ -247,10 +247,64 @@ export function createDocumentResource(options, vm) {
|
|
|
247
247
|
return out
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
function createListResource(options, vm, getResource) {
|
|
251
|
+
if (!options.doctype) return
|
|
252
|
+
|
|
253
|
+
let out = reactive({
|
|
254
|
+
doctype: options.doctype,
|
|
255
|
+
fields: options.fields,
|
|
256
|
+
filters: options.filters,
|
|
257
|
+
data: null,
|
|
258
|
+
list: createResource({
|
|
259
|
+
method: 'frappe.client.get_list',
|
|
260
|
+
makeParams() {
|
|
261
|
+
return {
|
|
262
|
+
doctype: out.doctype,
|
|
263
|
+
fields: out.fields,
|
|
264
|
+
filters: out.filters,
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
onSuccess(data) {
|
|
268
|
+
out.data = data
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
insert: createResource({
|
|
272
|
+
method: 'frappe.client.insert',
|
|
273
|
+
makeParams(values) {
|
|
274
|
+
return {
|
|
275
|
+
doc: {
|
|
276
|
+
doctype: out.doctype,
|
|
277
|
+
...values,
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
onSuccess() {
|
|
282
|
+
out.list.fetch()
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
update,
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
function update(updatedOptions) {
|
|
289
|
+
out.doctype = updatedOptions.doctype
|
|
290
|
+
out.fields = updatedOptions.fields
|
|
291
|
+
out.filters = updatedOptions.filters
|
|
292
|
+
out.list.fetch()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// fetch list
|
|
296
|
+
out.list.fetch()
|
|
297
|
+
|
|
298
|
+
return out
|
|
299
|
+
}
|
|
300
|
+
|
|
250
301
|
function createResourceForOptions(options, vm, getResource) {
|
|
251
302
|
if (options.type === 'document') {
|
|
252
303
|
return createDocumentResource(options, vm, getResource)
|
|
253
304
|
}
|
|
305
|
+
if (options.type === 'list') {
|
|
306
|
+
return createListResource(options, vm, getResource)
|
|
307
|
+
}
|
|
254
308
|
return createResource(options, vm, getResource)
|
|
255
309
|
}
|
|
256
310
|
|