frappe-ui 0.0.12 → 0.0.16
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 -2
- package/src/components/Avatar.vue +1 -1
- package/src/components/Dropdown.vue +6 -6
- package/src/components/Input.vue +6 -4
- package/src/components/Popover.vue +45 -19
- package/src/components/TextEditor/Menu.vue +4 -93
- package/src/components/TextEditor/TextEditor.vue +115 -11
- package/src/components/TextEditor/commands.js +79 -0
- package/src/components/Toast.vue +135 -0
- package/src/utils/resources.js +114 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
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",
|
|
@@ -86,25 +86,25 @@ export default {
|
|
|
86
86
|
onClick,
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
dropdownItems() {
|
|
92
|
-
return (this.options || [])
|
|
89
|
+
filterOptions(options) {
|
|
90
|
+
return (options || [])
|
|
93
91
|
.filter(Boolean)
|
|
94
92
|
.filter((option) => (option.condition ? option.condition() : true))
|
|
95
93
|
.map((option) => this.normalizeDropdownItem(option))
|
|
96
94
|
},
|
|
95
|
+
},
|
|
96
|
+
computed: {
|
|
97
97
|
groups() {
|
|
98
98
|
let groups = this.options[0]?.group
|
|
99
99
|
? this.options
|
|
100
|
-
: [{ group: '', items: this.options }]
|
|
100
|
+
: [{ group: '', items: this.filterOptions(this.options) }]
|
|
101
101
|
|
|
102
102
|
return groups.map((group, i) => {
|
|
103
103
|
return {
|
|
104
104
|
key: i,
|
|
105
105
|
group: group.group,
|
|
106
106
|
hideLabel: group.hideLabel || false,
|
|
107
|
-
items: group.items
|
|
107
|
+
items: this.filterOptions(group.items),
|
|
108
108
|
}
|
|
109
109
|
})
|
|
110
110
|
},
|
package/src/components/Input.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<label :class="type == 'checkbox' ? 'flex' : 'block'">
|
|
2
|
+
<label :class="[type == 'checkbox' ? 'flex' : 'block', $attrs.class]">
|
|
3
3
|
<span
|
|
4
4
|
v-if="label && type != 'checkbox'"
|
|
5
5
|
class="block mb-2 text-sm leading-4 text-gray-700"
|
|
@@ -68,6 +68,7 @@ import { debounce } from 'frappe-ui'
|
|
|
68
68
|
export default {
|
|
69
69
|
name: 'Input',
|
|
70
70
|
inheritAttrs: false,
|
|
71
|
+
expose: ['getInputValue'],
|
|
71
72
|
props: {
|
|
72
73
|
label: {
|
|
73
74
|
type: String,
|
|
@@ -122,9 +123,10 @@ export default {
|
|
|
122
123
|
this.$refs.input.blur()
|
|
123
124
|
},
|
|
124
125
|
getInputValue(e) {
|
|
125
|
-
let
|
|
126
|
+
let $input = e ? e.target : this.$refs.input
|
|
127
|
+
let value = $input.value
|
|
126
128
|
if (this.type == 'checkbox') {
|
|
127
|
-
value =
|
|
129
|
+
value = $input.checked
|
|
128
130
|
}
|
|
129
131
|
return value
|
|
130
132
|
},
|
|
@@ -134,7 +136,7 @@ export default {
|
|
|
134
136
|
if ('value' in this.$attrs) {
|
|
135
137
|
return this.$attrs.value
|
|
136
138
|
}
|
|
137
|
-
return this.modelValue
|
|
139
|
+
return this.modelValue || null
|
|
138
140
|
},
|
|
139
141
|
inputAttributes() {
|
|
140
142
|
let onInput = (e) => {
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="reference">
|
|
3
|
-
<div
|
|
4
|
-
|
|
3
|
+
<div
|
|
4
|
+
class="h-full"
|
|
5
|
+
ref="target"
|
|
6
|
+
@click="updatePosition"
|
|
7
|
+
@focusin="updatePosition"
|
|
8
|
+
@keydown="updatePosition"
|
|
9
|
+
>
|
|
10
|
+
<slot
|
|
11
|
+
name="target"
|
|
12
|
+
v-bind="{ togglePopover, updatePosition, open, close }"
|
|
13
|
+
></slot>
|
|
5
14
|
</div>
|
|
6
|
-
<teleport to="#
|
|
15
|
+
<teleport to="#frappeui-popper-root">
|
|
7
16
|
<div
|
|
8
17
|
ref="popover"
|
|
9
18
|
:class="popoverClass"
|
|
10
|
-
class="
|
|
11
|
-
|
|
19
|
+
class="relative z-[100] popover-container"
|
|
20
|
+
:style="{ minWidth: targetWidth ? targetWidth + 'px' : null }"
|
|
12
21
|
>
|
|
13
22
|
<div v-if="!hideArrow" class="popover-arrow" ref="popover-arrow"></div>
|
|
14
|
-
<slot
|
|
23
|
+
<slot
|
|
24
|
+
name="content"
|
|
25
|
+
v-bind="{ togglePopover, updatePosition, open, close }"
|
|
26
|
+
></slot>
|
|
15
27
|
</div>
|
|
16
28
|
</teleport>
|
|
17
29
|
</div>
|
|
@@ -25,9 +37,9 @@ export default {
|
|
|
25
37
|
props: {
|
|
26
38
|
hideArrow: {
|
|
27
39
|
type: Boolean,
|
|
28
|
-
default:
|
|
40
|
+
default: true,
|
|
29
41
|
},
|
|
30
|
-
|
|
42
|
+
show: {
|
|
31
43
|
default: null,
|
|
32
44
|
},
|
|
33
45
|
right: Boolean,
|
|
@@ -39,18 +51,28 @@ export default {
|
|
|
39
51
|
},
|
|
40
52
|
emits: ['init', 'open', 'close'],
|
|
41
53
|
watch: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
show: {
|
|
55
|
+
immediate: true,
|
|
56
|
+
handler(val) {
|
|
57
|
+
if (val) {
|
|
58
|
+
this.open()
|
|
59
|
+
} else {
|
|
60
|
+
this.close()
|
|
61
|
+
}
|
|
62
|
+
},
|
|
49
63
|
},
|
|
50
64
|
},
|
|
51
65
|
data() {
|
|
52
66
|
return {
|
|
53
67
|
isOpen: false,
|
|
68
|
+
targetWidth: null,
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
created() {
|
|
72
|
+
if (!document.getElementById('frappeui-popper-root')) {
|
|
73
|
+
const root = document.createElement('div')
|
|
74
|
+
root.id = 'frappeui-popper-root'
|
|
75
|
+
document.body.appendChild(root)
|
|
54
76
|
}
|
|
55
77
|
},
|
|
56
78
|
mounted() {
|
|
@@ -67,6 +89,9 @@ export default {
|
|
|
67
89
|
if (this.show == null) {
|
|
68
90
|
document.addEventListener('click', this.listener)
|
|
69
91
|
}
|
|
92
|
+
this.$nextTick(() => {
|
|
93
|
+
this.targetWidth = this.$refs['target'].clientWidth
|
|
94
|
+
})
|
|
70
95
|
},
|
|
71
96
|
beforeDestroy() {
|
|
72
97
|
this.popper && this.popper.destroy()
|
|
@@ -95,10 +120,13 @@ export default {
|
|
|
95
120
|
: [],
|
|
96
121
|
})
|
|
97
122
|
} else {
|
|
98
|
-
this.
|
|
123
|
+
this.updatePosition()
|
|
99
124
|
}
|
|
100
125
|
this.$emit('init')
|
|
101
126
|
},
|
|
127
|
+
updatePosition() {
|
|
128
|
+
this.popper && this.popper.update()
|
|
129
|
+
},
|
|
102
130
|
togglePopover(flag) {
|
|
103
131
|
if (flag == null) {
|
|
104
132
|
flag = !this.isOpen
|
|
@@ -115,9 +143,7 @@ export default {
|
|
|
115
143
|
return
|
|
116
144
|
}
|
|
117
145
|
this.isOpen = true
|
|
118
|
-
this.$nextTick(() =>
|
|
119
|
-
this.setupPopper()
|
|
120
|
-
})
|
|
146
|
+
this.$nextTick(() => this.setupPopper())
|
|
121
147
|
this.$emit('open')
|
|
122
148
|
},
|
|
123
149
|
close() {
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<teleport to="#frappeui-toast-root">
|
|
3
|
+
<transition :name="position.includes('top') ? 'toast-top' : 'toast-bottom'">
|
|
4
|
+
<div
|
|
5
|
+
v-if="shown"
|
|
6
|
+
:style="style"
|
|
7
|
+
:class="[
|
|
8
|
+
'absolute transition duration-200 ease-out m-4 pointer-events-auto',
|
|
9
|
+
position.includes('center') ? '-translate-x-1/2' : '',
|
|
10
|
+
]"
|
|
11
|
+
>
|
|
12
|
+
<div
|
|
13
|
+
class="px-2.5 py-2 bg-white border rounded-lg shadow-md min-w-[15rem]"
|
|
14
|
+
>
|
|
15
|
+
<div class="flex items-center justify-between">
|
|
16
|
+
<div class="text-lg">
|
|
17
|
+
<slot> Toast Content </slot>
|
|
18
|
+
</div>
|
|
19
|
+
<div>
|
|
20
|
+
<slot name="actions">
|
|
21
|
+
<Button icon="x" @click="shown = false" />
|
|
22
|
+
</slot>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</transition>
|
|
28
|
+
</teleport>
|
|
29
|
+
</template>
|
|
30
|
+
<script>
|
|
31
|
+
const positions = [
|
|
32
|
+
'top-right',
|
|
33
|
+
'top-center',
|
|
34
|
+
'top-left',
|
|
35
|
+
'bottom-right',
|
|
36
|
+
'bottom-center',
|
|
37
|
+
'bottom-left',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
name: 'Toast',
|
|
42
|
+
props: {
|
|
43
|
+
position: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: 'top-right',
|
|
46
|
+
},
|
|
47
|
+
text: {
|
|
48
|
+
type: String,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
created() {
|
|
52
|
+
if (!document.getElementById('frappeui-toast-root')) {
|
|
53
|
+
const root = document.createElement('div')
|
|
54
|
+
root.id = 'frappeui-toast-root'
|
|
55
|
+
root.style.position = 'fixed'
|
|
56
|
+
root.style.top = '16px'
|
|
57
|
+
root.style.right = '16px'
|
|
58
|
+
root.style.bottom = '16px'
|
|
59
|
+
root.style.left = '16px'
|
|
60
|
+
root.style.zIndex = '9999'
|
|
61
|
+
root.style.pointerEvents = 'none'
|
|
62
|
+
document.body.appendChild(root)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
mounted() {
|
|
66
|
+
this.shown = true
|
|
67
|
+
// setTimeout(() => {
|
|
68
|
+
// this.shown = false
|
|
69
|
+
// }, 3000)
|
|
70
|
+
},
|
|
71
|
+
data() {
|
|
72
|
+
return {
|
|
73
|
+
shown: false,
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
computed: {
|
|
77
|
+
style() {
|
|
78
|
+
let style = {}
|
|
79
|
+
if (this.position.includes('top')) {
|
|
80
|
+
style.top = 0
|
|
81
|
+
}
|
|
82
|
+
if (this.position.includes('bottom')) {
|
|
83
|
+
style.bottom = 0
|
|
84
|
+
}
|
|
85
|
+
if (this.position.includes('right')) {
|
|
86
|
+
style.right = 0
|
|
87
|
+
}
|
|
88
|
+
if (this.position.includes('left')) {
|
|
89
|
+
style.left = 0
|
|
90
|
+
}
|
|
91
|
+
if (this.position.includes('center')) {
|
|
92
|
+
style.left = '50%'
|
|
93
|
+
// style.transform = 'translateX(-50%)'
|
|
94
|
+
}
|
|
95
|
+
return style
|
|
96
|
+
},
|
|
97
|
+
transitionProps() {
|
|
98
|
+
let props = {
|
|
99
|
+
enterActiveClass: 'transition duration-200 ease-out',
|
|
100
|
+
enterFromClass: 'opacity-0',
|
|
101
|
+
enterToClass: 'translate-y-0 opacity-100',
|
|
102
|
+
leaveActiveClass: 'transition duration-100 ease-in',
|
|
103
|
+
leaveFromClass: 'scale-100 translate-y-0 opacity-100',
|
|
104
|
+
leaveToClass: 'scale-75 translate-y-4 opacity-0',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (this.position.includes('top')) {
|
|
108
|
+
props.enterFromClass += ' -translate-y-12'
|
|
109
|
+
}
|
|
110
|
+
if (this.position.includes('bottom')) {
|
|
111
|
+
props.enterFromClass += ' translate-y-12'
|
|
112
|
+
}
|
|
113
|
+
return props
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
</script>
|
|
118
|
+
<style>
|
|
119
|
+
.toast-top-enter-active,
|
|
120
|
+
.toast-bottom-enter-active {
|
|
121
|
+
transition: all 200ms ease-out;
|
|
122
|
+
}
|
|
123
|
+
.toast-top-leave-active,
|
|
124
|
+
.toast-bottom-leave-active {
|
|
125
|
+
transition: all 100ms ease-in;
|
|
126
|
+
}
|
|
127
|
+
.toast-top-enter-from {
|
|
128
|
+
opacity: 0;
|
|
129
|
+
transform: translateY(0);
|
|
130
|
+
}
|
|
131
|
+
.toast-top-enter-to {
|
|
132
|
+
opacity: 1;
|
|
133
|
+
transform: translateY(0);
|
|
134
|
+
}
|
|
135
|
+
</style>
|
package/src/utils/resources.js
CHANGED
|
@@ -41,7 +41,7 @@ export function createResource(options, vm, getResource) {
|
|
|
41
41
|
setData,
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
async function fetch(params) {
|
|
44
|
+
async function fetch(params, tempOptions = {}) {
|
|
45
45
|
if (params instanceof Event) {
|
|
46
46
|
params = null
|
|
47
47
|
}
|
|
@@ -57,18 +57,22 @@ export function createResource(options, vm, getResource) {
|
|
|
57
57
|
options.onFetch.call(vm, out.params)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
let validateFunction = tempOptions.validate || options.validate
|
|
61
|
+
let errorFunction = tempOptions.onError || options.onError
|
|
62
|
+
let successFunction = tempOptions.onSuccess || options.onSuccess
|
|
63
|
+
|
|
64
|
+
if (validateFunction) {
|
|
61
65
|
let invalidMessage
|
|
62
66
|
try {
|
|
63
|
-
invalidMessage = await
|
|
67
|
+
invalidMessage = await validateFunction.call(vm, out.params)
|
|
64
68
|
if (invalidMessage && typeof invalidMessage == 'string') {
|
|
65
69
|
let error = new Error(invalidMessage)
|
|
66
|
-
handleError(error)
|
|
70
|
+
handleError(error, errorFunction)
|
|
67
71
|
out.loading = false
|
|
68
72
|
return
|
|
69
73
|
}
|
|
70
74
|
} catch (error) {
|
|
71
|
-
handleError(error)
|
|
75
|
+
handleError(error, errorFunction)
|
|
72
76
|
out.loading = false
|
|
73
77
|
return
|
|
74
78
|
}
|
|
@@ -78,11 +82,11 @@ export function createResource(options, vm, getResource) {
|
|
|
78
82
|
let data = await resourceFetcher(options.method, params || options.params)
|
|
79
83
|
out.data = data
|
|
80
84
|
out.fetched = true
|
|
81
|
-
if (
|
|
82
|
-
|
|
85
|
+
if (successFunction) {
|
|
86
|
+
successFunction.call(vm, data)
|
|
83
87
|
}
|
|
84
88
|
} catch (error) {
|
|
85
|
-
handleError(error)
|
|
89
|
+
handleError(error, errorFunction)
|
|
86
90
|
}
|
|
87
91
|
out.loading = false
|
|
88
92
|
}
|
|
@@ -109,14 +113,14 @@ export function createResource(options, vm, getResource) {
|
|
|
109
113
|
out.auto = options.auto
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
function handleError(error) {
|
|
116
|
+
function handleError(error, errorFunction) {
|
|
113
117
|
console.error(error)
|
|
114
118
|
if (out.previousData) {
|
|
115
119
|
out.data = out.previousData
|
|
116
120
|
}
|
|
117
121
|
out.error = error
|
|
118
|
-
if (
|
|
119
|
-
|
|
122
|
+
if (errorFunction) {
|
|
123
|
+
errorFunction.call(vm, error)
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -138,6 +142,8 @@ export function createResource(options, vm, getResource) {
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
export function createDocumentResource(options, vm) {
|
|
145
|
+
if (!(options.doctype && options.name)) return
|
|
146
|
+
|
|
141
147
|
let cacheKey = getCacheKey([options.doctype, options.name])
|
|
142
148
|
if (documentCache[cacheKey]) {
|
|
143
149
|
return documentCache[cacheKey]
|
|
@@ -153,7 +159,7 @@ export function createDocumentResource(options, vm) {
|
|
|
153
159
|
}
|
|
154
160
|
},
|
|
155
161
|
onSuccess(data) {
|
|
156
|
-
out.doc = data
|
|
162
|
+
out.doc = postprocess(data)
|
|
157
163
|
},
|
|
158
164
|
}
|
|
159
165
|
|
|
@@ -170,7 +176,7 @@ export function createDocumentResource(options, vm) {
|
|
|
170
176
|
}
|
|
171
177
|
},
|
|
172
178
|
onSuccess(data) {
|
|
173
|
-
out.doc = data
|
|
179
|
+
out.doc = postprocess(data)
|
|
174
180
|
},
|
|
175
181
|
}),
|
|
176
182
|
setValue: createResource(setValueOptions),
|
|
@@ -191,14 +197,54 @@ export function createDocumentResource(options, vm) {
|
|
|
191
197
|
},
|
|
192
198
|
}),
|
|
193
199
|
update,
|
|
200
|
+
reload,
|
|
194
201
|
})
|
|
195
202
|
|
|
203
|
+
for (let method in options.whitelistedMethods) {
|
|
204
|
+
let methodName = options.whitelistedMethods[method]
|
|
205
|
+
out[method] = createResource({
|
|
206
|
+
method: 'run_doc_method',
|
|
207
|
+
makeParams(values) {
|
|
208
|
+
return {
|
|
209
|
+
dt: out.doctype,
|
|
210
|
+
dn: out.name,
|
|
211
|
+
method: methodName,
|
|
212
|
+
args: JSON.stringify(values),
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onSuccess(data) {
|
|
216
|
+
if (data.docs) {
|
|
217
|
+
for (let doc of data.docs) {
|
|
218
|
+
if (doc.doctype === out.doctype && doc.name === out.name) {
|
|
219
|
+
out.doc = postprocess(doc)
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
196
228
|
function update(updatedOptions) {
|
|
197
229
|
out.doctype = updatedOptions.doctype
|
|
198
230
|
out.name = updatedOptions.name
|
|
199
231
|
out.get.fetch()
|
|
200
232
|
}
|
|
201
233
|
|
|
234
|
+
function reload() {
|
|
235
|
+
out.get.fetch()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function postprocess(doc) {
|
|
239
|
+
if (options.postprocess) {
|
|
240
|
+
let returnValue = options.postprocess(doc)
|
|
241
|
+
if (typeof returnValue === 'object') {
|
|
242
|
+
return returnValue
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return doc
|
|
246
|
+
}
|
|
247
|
+
|
|
202
248
|
// fetch the doc
|
|
203
249
|
out.get.fetch()
|
|
204
250
|
// cache
|
|
@@ -206,10 +252,64 @@ export function createDocumentResource(options, vm) {
|
|
|
206
252
|
return out
|
|
207
253
|
}
|
|
208
254
|
|
|
255
|
+
function createListResource(options, vm, getResource) {
|
|
256
|
+
if (!options.doctype) return
|
|
257
|
+
|
|
258
|
+
let out = reactive({
|
|
259
|
+
doctype: options.doctype,
|
|
260
|
+
fields: options.fields,
|
|
261
|
+
filters: options.filters,
|
|
262
|
+
data: null,
|
|
263
|
+
list: createResource({
|
|
264
|
+
method: 'frappe.client.get_list',
|
|
265
|
+
makeParams() {
|
|
266
|
+
return {
|
|
267
|
+
doctype: out.doctype,
|
|
268
|
+
fields: out.fields,
|
|
269
|
+
filters: out.filters,
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
onSuccess(data) {
|
|
273
|
+
out.data = data
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
insert: createResource({
|
|
277
|
+
method: 'frappe.client.insert',
|
|
278
|
+
makeParams(values) {
|
|
279
|
+
return {
|
|
280
|
+
doc: {
|
|
281
|
+
doctype: out.doctype,
|
|
282
|
+
...values,
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
onSuccess() {
|
|
287
|
+
out.list.fetch()
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
update,
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
function update(updatedOptions) {
|
|
294
|
+
out.doctype = updatedOptions.doctype
|
|
295
|
+
out.fields = updatedOptions.fields
|
|
296
|
+
out.filters = updatedOptions.filters
|
|
297
|
+
out.list.fetch()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// fetch list
|
|
301
|
+
out.list.fetch()
|
|
302
|
+
|
|
303
|
+
return out
|
|
304
|
+
}
|
|
305
|
+
|
|
209
306
|
function createResourceForOptions(options, vm, getResource) {
|
|
210
307
|
if (options.type === 'document') {
|
|
211
308
|
return createDocumentResource(options, vm, getResource)
|
|
212
309
|
}
|
|
310
|
+
if (options.type === 'list') {
|
|
311
|
+
return createListResource(options, vm, getResource)
|
|
312
|
+
}
|
|
213
313
|
return createResource(options, vm, getResource)
|
|
214
314
|
}
|
|
215
315
|
|
|
@@ -248,7 +348,7 @@ let createMixin = (mixinOptions) => ({
|
|
|
248
348
|
} else {
|
|
249
349
|
resource.update(updatedOptions)
|
|
250
350
|
}
|
|
251
|
-
if (resource.auto) {
|
|
351
|
+
if (resource && resource.auto) {
|
|
252
352
|
resource.fetch()
|
|
253
353
|
}
|
|
254
354
|
},
|