frappe-ui 0.0.57 → 0.0.60
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/Autocomplete.vue +51 -21
- package/src/components/Badge.vue +14 -18
- package/src/components/TextEditor/MentionList.vue +17 -15
- package/src/components/TextEditor/TextEditor.vue +24 -5
- package/src/components/TextEditor/commands.js +2 -2
- package/src/directives/onOutsideClick.js +17 -11
- package/src/index.js +1 -1
- package/src/resources/documentResource.js +171 -0
- package/src/resources/index.js +3 -0
- package/src/resources/listResource.js +266 -0
- package/src/resources/local.js +10 -0
- package/src/resources/plugin.js +108 -0
- package/src/resources/resources.js +188 -0
- package/src/utils/plugin.js +2 -2
- package/src/utils/resources.js +0 -661
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@tiptap/vue-3": "^2.0.0-beta.96",
|
|
37
37
|
"autoprefixer": "^10.4.2",
|
|
38
38
|
"feather-icons": "^4.28.0",
|
|
39
|
+
"idb-keyval": "^6.2.0",
|
|
39
40
|
"postcss": "^8.4.5",
|
|
40
41
|
"socket.io-client": "^4.5.1",
|
|
41
42
|
"tailwindcss": "^3.0.12",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
</template>
|
|
32
32
|
<template #body>
|
|
33
33
|
<ComboboxOptions
|
|
34
|
-
class="max-h-[
|
|
34
|
+
class="max-h-[15rem] overflow-y-auto rounded-md rounded-t-none bg-white px-1.5 pb-1.5 shadow-md"
|
|
35
35
|
static
|
|
36
36
|
v-show="isComboboxOpen"
|
|
37
37
|
>
|
|
@@ -52,24 +52,36 @@
|
|
|
52
52
|
/>
|
|
53
53
|
<Button icon="x" @click="selectedValue = null" />
|
|
54
54
|
</div>
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
:value="option"
|
|
60
|
-
v-slot="{ active, selected }"
|
|
55
|
+
<div
|
|
56
|
+
v-for="group in groups"
|
|
57
|
+
:key="group.key"
|
|
58
|
+
v-show="group.items.length > 0"
|
|
61
59
|
>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
<div
|
|
61
|
+
v-if="group.group && !group.hideLabel"
|
|
62
|
+
class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
|
|
63
|
+
>
|
|
64
|
+
{{ group.group }}
|
|
65
|
+
</div>
|
|
66
|
+
<ComboboxOption
|
|
67
|
+
as="template"
|
|
68
|
+
v-for="option in group.items"
|
|
69
|
+
:key="option.value"
|
|
70
|
+
:value="option"
|
|
71
|
+
v-slot="{ active, selected }"
|
|
67
72
|
>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
<li
|
|
74
|
+
:class="[
|
|
75
|
+
'rounded-md px-2.5 py-1.5 text-base',
|
|
76
|
+
{ 'bg-gray-100': active },
|
|
77
|
+
]"
|
|
78
|
+
>
|
|
79
|
+
{{ option.label }}
|
|
80
|
+
</li>
|
|
81
|
+
</ComboboxOption>
|
|
82
|
+
</div>
|
|
71
83
|
<li
|
|
72
|
-
v-if="
|
|
84
|
+
v-if="groups.length == 0"
|
|
73
85
|
class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
|
|
74
86
|
>
|
|
75
87
|
No results found
|
|
@@ -119,11 +131,31 @@ export default {
|
|
|
119
131
|
this.$emit(this.valuePropPassed ? 'change' : 'update:modelValue', val)
|
|
120
132
|
},
|
|
121
133
|
},
|
|
122
|
-
|
|
134
|
+
groups() {
|
|
135
|
+
if (!this.options || this.options.length == 0) return []
|
|
136
|
+
|
|
137
|
+
let groups = this.options[0]?.group
|
|
138
|
+
? this.options
|
|
139
|
+
: [{ group: '', items: this.options }]
|
|
140
|
+
|
|
141
|
+
return groups
|
|
142
|
+
.map((group, i) => {
|
|
143
|
+
return {
|
|
144
|
+
key: i,
|
|
145
|
+
group: group.group,
|
|
146
|
+
hideLabel: group.hideLabel || false,
|
|
147
|
+
items: this.filterOptions(group.items),
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.filter((group) => group.items.length > 0)
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
methods: {
|
|
154
|
+
filterOptions(options) {
|
|
123
155
|
if (!this.query) {
|
|
124
|
-
return
|
|
156
|
+
return options
|
|
125
157
|
}
|
|
126
|
-
return
|
|
158
|
+
return options.filter((option) => {
|
|
127
159
|
let searchTexts = [option.label, option.value]
|
|
128
160
|
return searchTexts.some((text) =>
|
|
129
161
|
(text || '')
|
|
@@ -133,8 +165,6 @@ export default {
|
|
|
133
165
|
)
|
|
134
166
|
})
|
|
135
167
|
},
|
|
136
|
-
},
|
|
137
|
-
methods: {
|
|
138
168
|
displayValue(option) {
|
|
139
169
|
if (typeof option === 'string') {
|
|
140
170
|
return option
|
package/src/components/Badge.vue
CHANGED
|
@@ -7,23 +7,22 @@
|
|
|
7
7
|
</span>
|
|
8
8
|
</template>
|
|
9
9
|
<script>
|
|
10
|
+
const DEFAULT_COLOR_MAP = {
|
|
11
|
+
Pending: 'yellow',
|
|
12
|
+
Running: 'yellow',
|
|
13
|
+
Success: 'green',
|
|
14
|
+
Failure: 'red',
|
|
15
|
+
Active: 'green',
|
|
16
|
+
Broken: 'red',
|
|
17
|
+
Updating: 'blue',
|
|
18
|
+
Rejected: 'red',
|
|
19
|
+
Published: 'green',
|
|
20
|
+
Approved: 'green',
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export default {
|
|
11
24
|
name: 'Badge',
|
|
12
25
|
props: ['color', 'status', 'colorMap'],
|
|
13
|
-
data: {
|
|
14
|
-
defaultColorMap: {
|
|
15
|
-
Pending: 'yellow',
|
|
16
|
-
Running: 'yellow',
|
|
17
|
-
Success: 'green',
|
|
18
|
-
Failure: 'red',
|
|
19
|
-
Active: 'green',
|
|
20
|
-
Broken: 'red',
|
|
21
|
-
Updating: 'blue',
|
|
22
|
-
Rejected: 'red',
|
|
23
|
-
Published: 'green',
|
|
24
|
-
Approved: 'green',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
26
|
computed: {
|
|
28
27
|
classes() {
|
|
29
28
|
let color = this.getBadgeColor()
|
|
@@ -46,10 +45,7 @@ export default {
|
|
|
46
45
|
return color
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
let statusColorMap = Object.assign(
|
|
50
|
-
this.defaultColorMap,
|
|
51
|
-
this.colorMap || {}
|
|
52
|
-
)
|
|
48
|
+
let statusColorMap = Object.assign(DEFAULT_COLOR_MAP, this.colorMap || {})
|
|
53
49
|
color = statusColorMap[this.status] || 'gray'
|
|
54
50
|
|
|
55
51
|
return color
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<button
|
|
7
|
-
:class="[
|
|
8
|
-
index === selectedIndex ? 'bg-gray-100' : 'text-gray-900',
|
|
9
|
-
'flex w-full items-center whitespace-nowrap rounded-md px-2 py-2 text-sm',
|
|
10
|
-
]"
|
|
11
|
-
v-for="(item, index) in items"
|
|
12
|
-
:key="index"
|
|
13
|
-
@click="selectItem(index)"
|
|
14
|
-
@mouseover="selectedIndex = index"
|
|
2
|
+
<div>
|
|
3
|
+
<div
|
|
4
|
+
v-if="items.length"
|
|
5
|
+
class="min-w-40 rounded-lg border bg-white p-1 text-base shadow-lg"
|
|
15
6
|
>
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
<button
|
|
8
|
+
:class="[
|
|
9
|
+
index === selectedIndex ? 'bg-gray-100' : 'text-gray-900',
|
|
10
|
+
'flex w-full items-center whitespace-nowrap rounded-md px-2 py-2 text-sm',
|
|
11
|
+
]"
|
|
12
|
+
v-for="(item, index) in items"
|
|
13
|
+
:key="index"
|
|
14
|
+
@click="selectItem(index)"
|
|
15
|
+
@mouseover="selectedIndex = index"
|
|
16
|
+
>
|
|
17
|
+
{{ item.label }}
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
18
20
|
</div>
|
|
19
21
|
</template>
|
|
20
22
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
<div class="relative w-full" :class="$attrs.class" v-if="editor">
|
|
3
3
|
<BubbleMenu
|
|
4
4
|
v-if="bubbleMenuButtons"
|
|
5
|
-
class="bubble-menu"
|
|
5
|
+
class="bubble-menu rounded-md shadow-sm"
|
|
6
6
|
:tippy-options="{ duration: 100 }"
|
|
7
7
|
:editor="editor"
|
|
8
8
|
>
|
|
9
9
|
<Menu
|
|
10
10
|
:editor="editor"
|
|
11
|
-
class="rounded-md border border-gray-100 shadow-
|
|
11
|
+
class="rounded-md border border-gray-100 shadow-lg"
|
|
12
12
|
:buttons="bubbleMenuButtons"
|
|
13
13
|
/>
|
|
14
14
|
</BubbleMenu>
|
|
@@ -131,9 +131,11 @@ export default {
|
|
|
131
131
|
editorProps: {
|
|
132
132
|
deep: true,
|
|
133
133
|
handler(value) {
|
|
134
|
-
this.editor
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
if (this.editor) {
|
|
135
|
+
this.editor.setOptions({
|
|
136
|
+
editorProps: value,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
137
139
|
},
|
|
138
140
|
},
|
|
139
141
|
},
|
|
@@ -247,8 +249,25 @@ export default {
|
|
|
247
249
|
'Separator',
|
|
248
250
|
'Bullet List',
|
|
249
251
|
'Numbered List',
|
|
252
|
+
'Separator',
|
|
253
|
+
'Image',
|
|
250
254
|
'Blockquote',
|
|
251
255
|
'Code',
|
|
256
|
+
[
|
|
257
|
+
'InsertTable',
|
|
258
|
+
'AddColumnBefore',
|
|
259
|
+
'AddColumnAfter',
|
|
260
|
+
'DeleteColumn',
|
|
261
|
+
'AddRowBefore',
|
|
262
|
+
'AddRowAfter',
|
|
263
|
+
'DeleteRow',
|
|
264
|
+
'MergeCells',
|
|
265
|
+
'SplitCell',
|
|
266
|
+
'ToggleHeaderColumn',
|
|
267
|
+
'ToggleHeaderRow',
|
|
268
|
+
'ToggleHeaderCell',
|
|
269
|
+
'DeleteTable',
|
|
270
|
+
],
|
|
252
271
|
]
|
|
253
272
|
}
|
|
254
273
|
return buttons.map(createEditorButton)
|
|
@@ -97,13 +97,13 @@ export default {
|
|
|
97
97
|
},
|
|
98
98
|
'Bullet List': {
|
|
99
99
|
label: 'Bullet List',
|
|
100
|
-
icon:
|
|
100
|
+
icon: ListUnordered,
|
|
101
101
|
action: (editor) => editor.chain().focus().toggleBulletList().run(),
|
|
102
102
|
isActive: (editor) => editor.isActive('bulletList'),
|
|
103
103
|
},
|
|
104
104
|
'Numbered List': {
|
|
105
105
|
label: 'Numbered List',
|
|
106
|
-
icon:
|
|
106
|
+
icon: ListOrdered,
|
|
107
107
|
action: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
|
108
108
|
isActive: (editor) => editor.isActive('orderedList'),
|
|
109
109
|
},
|
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
const instanceMap = new Map()
|
|
2
2
|
|
|
3
3
|
function onDocumentClick(e, el, fn) {
|
|
4
4
|
let target = e.target
|
|
5
5
|
if (el !== target && !el.contains(target)) {
|
|
6
|
-
fn(e)
|
|
6
|
+
fn?.(e)
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
beforeMount(el, binding) {
|
|
12
|
-
el.dataset.outsideClickIndex = instances.length
|
|
13
|
-
|
|
14
12
|
const fn = binding.value
|
|
15
|
-
const
|
|
13
|
+
const clickHandler = function (e) {
|
|
16
14
|
onDocumentClick(e, el, fn)
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
removeHandlerIfPresent(el)
|
|
18
|
+
instanceMap.set(el, clickHandler)
|
|
19
|
+
document.addEventListener('click', clickHandler)
|
|
21
20
|
},
|
|
22
21
|
unmounted(el) {
|
|
23
|
-
|
|
24
|
-
const handler = instances[index]
|
|
25
|
-
document.addEventListener('click', handler)
|
|
26
|
-
instances.splice(index, 1)
|
|
22
|
+
removeHandlerIfPresent(el)
|
|
27
23
|
},
|
|
28
24
|
}
|
|
25
|
+
|
|
26
|
+
function removeHandlerIfPresent(el) {
|
|
27
|
+
const clickHandler = instanceMap.get(el)
|
|
28
|
+
if (!clickHandler) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
instanceMap.delete(el)
|
|
33
|
+
document.removeEventListener('click', clickHandler)
|
|
34
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { reactive } from 'vue'
|
|
2
|
+
import { getCacheKey, createResource } from './resources'
|
|
3
|
+
import {
|
|
4
|
+
updateRowInListResource,
|
|
5
|
+
deleteRowInListResource,
|
|
6
|
+
revertRowInListResource,
|
|
7
|
+
} from './listResource'
|
|
8
|
+
import { getLocal, saveLocal } from './local'
|
|
9
|
+
|
|
10
|
+
let documentCache = reactive({})
|
|
11
|
+
|
|
12
|
+
export function createDocumentResource(options, vm) {
|
|
13
|
+
if (!(options.doctype && options.name)) return
|
|
14
|
+
|
|
15
|
+
let cacheKey = getCacheKey([options.doctype, options.name])
|
|
16
|
+
if (documentCache[cacheKey]) {
|
|
17
|
+
return documentCache[cacheKey]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let setValueOptions = {
|
|
21
|
+
method: 'frappe.client.set_value',
|
|
22
|
+
makeParams(values) {
|
|
23
|
+
return {
|
|
24
|
+
doctype: out.doctype,
|
|
25
|
+
name: out.name,
|
|
26
|
+
fieldname: values,
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
beforeSubmit(params) {
|
|
30
|
+
out.previousDoc = JSON.stringify(out.doc)
|
|
31
|
+
Object.assign(out.doc, params.fieldname || {})
|
|
32
|
+
// update data in list resources
|
|
33
|
+
updateRowInListResource(out.doctype, out.doc)
|
|
34
|
+
},
|
|
35
|
+
onSuccess(data) {
|
|
36
|
+
out.doc = transform(data)
|
|
37
|
+
options.setValue?.onSuccess?.call(vm, data)
|
|
38
|
+
},
|
|
39
|
+
onError(error) {
|
|
40
|
+
out.doc = JSON.parse(out.previousDoc)
|
|
41
|
+
options.setValue?.onError?.call(vm, error)
|
|
42
|
+
// revert data in list resource
|
|
43
|
+
revertRowInListResource(out.doctype, out.doc)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let out = reactive({
|
|
48
|
+
doctype: options.doctype,
|
|
49
|
+
name: options.name,
|
|
50
|
+
doc: null,
|
|
51
|
+
auto: true,
|
|
52
|
+
get: createResource(
|
|
53
|
+
{
|
|
54
|
+
method: 'frappe.client.get',
|
|
55
|
+
makeParams() {
|
|
56
|
+
return {
|
|
57
|
+
doctype: out.doctype,
|
|
58
|
+
name: out.name,
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
onSuccess(data) {
|
|
62
|
+
saveLocal(cacheKey, data)
|
|
63
|
+
out.doc = transform(data)
|
|
64
|
+
options.onSuccess?.call(vm, out.doc)
|
|
65
|
+
},
|
|
66
|
+
onError: options.onError,
|
|
67
|
+
},
|
|
68
|
+
vm
|
|
69
|
+
),
|
|
70
|
+
setValue: createResource(setValueOptions, vm),
|
|
71
|
+
setValueDebounced: createResource(
|
|
72
|
+
{
|
|
73
|
+
...setValueOptions,
|
|
74
|
+
debounce: options.debounce || 500,
|
|
75
|
+
},
|
|
76
|
+
vm
|
|
77
|
+
),
|
|
78
|
+
delete: createResource(
|
|
79
|
+
{
|
|
80
|
+
method: 'frappe.client.delete',
|
|
81
|
+
makeParams() {
|
|
82
|
+
return {
|
|
83
|
+
doctype: out.doctype,
|
|
84
|
+
name: out.name,
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
onSuccess() {
|
|
88
|
+
out.doc = null
|
|
89
|
+
options.delete?.onSuccess?.call(vm)
|
|
90
|
+
// delete from list resources
|
|
91
|
+
deleteRowInListResource(out.doctype, out.name)
|
|
92
|
+
},
|
|
93
|
+
onError: options.delete?.onError,
|
|
94
|
+
},
|
|
95
|
+
vm
|
|
96
|
+
),
|
|
97
|
+
update,
|
|
98
|
+
reload,
|
|
99
|
+
setDoc,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
for (let method in options.whitelistedMethods) {
|
|
103
|
+
let methodName = options.whitelistedMethods[method]
|
|
104
|
+
out[method] = createResource(
|
|
105
|
+
{
|
|
106
|
+
method: 'run_doc_method',
|
|
107
|
+
makeParams(values) {
|
|
108
|
+
return {
|
|
109
|
+
dt: out.doctype,
|
|
110
|
+
dn: out.name,
|
|
111
|
+
method: methodName,
|
|
112
|
+
args: JSON.stringify(values),
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
onSuccess(data) {
|
|
116
|
+
if (data.docs) {
|
|
117
|
+
for (let doc of data.docs) {
|
|
118
|
+
if (doc.doctype === out.doctype && doc.name === out.name) {
|
|
119
|
+
out.doc = transform(doc)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
vm
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function update(updatedOptions) {
|
|
131
|
+
out.doctype = updatedOptions.doctype
|
|
132
|
+
out.name = updatedOptions.name
|
|
133
|
+
out.get.fetch()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function reload() {
|
|
137
|
+
return out.get.fetch()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setDoc(doc) {
|
|
141
|
+
if (typeof doc === 'function') {
|
|
142
|
+
doc = doc.call(vm, out.doc)
|
|
143
|
+
}
|
|
144
|
+
out.doc = transform(doc)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function transform(doc) {
|
|
148
|
+
if (options.transform) {
|
|
149
|
+
let returnValue = options.transform.call(vm, doc)
|
|
150
|
+
if (typeof returnValue === 'object') {
|
|
151
|
+
return returnValue
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return doc
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// cache
|
|
158
|
+
documentCache[cacheKey] = out
|
|
159
|
+
// offline
|
|
160
|
+
getLocal(cacheKey).then((data) => {
|
|
161
|
+
if (out.get.loading && data) {
|
|
162
|
+
out.doc = transform(data)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
return out
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getCachedDocumentResource(doctype, name) {
|
|
169
|
+
let cacheKey = getCacheKey([doctype, name])
|
|
170
|
+
return documentCache[cacheKey] || null
|
|
171
|
+
}
|