frappe-ui 0.1.209 → 0.1.211
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/frappe/Link/Link.vue +101 -0
- package/frappe/Link/index.ts +2 -0
- package/frappe/Link/types.ts +10 -0
- package/frappe/index.js +3 -0
- package/package.json +1 -1
- package/src/components/Autocomplete/Autocomplete.vue +1 -4
- package/src/components/Combobox/Combobox.vue +2 -0
- package/src/components/TextEditor/TextEditor.vue +2 -10
- package/src/components/TextEditor/extensions/list-extension.ts +32 -0
- package/src/index.ts +0 -1
- package/src/components/Link.vue +0 -28
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-1.5" :class="attrs.class" :style="attrs.style">
|
|
3
|
+
<FormLabel v-if="label" :label="label" size="sm" :required="required" />
|
|
4
|
+
<Combobox
|
|
5
|
+
v-model="model"
|
|
6
|
+
:placeholder="placeholder || `Select ${doctype}`"
|
|
7
|
+
:options="linkOptions"
|
|
8
|
+
@input="handleInputChange"
|
|
9
|
+
@focus="() => loadOptions('')"
|
|
10
|
+
:open-on-focus="true"
|
|
11
|
+
v-bind="attrsWithoutClassStyle"
|
|
12
|
+
>
|
|
13
|
+
<template #create-new="{ searchTerm }">
|
|
14
|
+
<LucidePlus class="size-4 mr-2" />
|
|
15
|
+
<span class="font-medium"> Create new {{ doctype }}</span>
|
|
16
|
+
</template>
|
|
17
|
+
</Combobox>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { watch, useAttrs, computed } from 'vue'
|
|
23
|
+
import { Combobox, type ComboboxOption } from '../../src/components/Combobox'
|
|
24
|
+
import FormLabel from '../../src/components/FormLabel.vue'
|
|
25
|
+
import debounce from '../../src/utils/debounce'
|
|
26
|
+
// @ts-ignore - Vue SFC without explicit types
|
|
27
|
+
import { createResource } from '../../src/resources'
|
|
28
|
+
import { frappeRequest } from '../../src/utils/frappeRequest'
|
|
29
|
+
import type { LinkProps, SelectOption } from './types'
|
|
30
|
+
import LucidePlus from '~icons/lucide/plus'
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<LinkProps>(), {
|
|
33
|
+
label: '',
|
|
34
|
+
filters: () => ({}),
|
|
35
|
+
})
|
|
36
|
+
const model = defineModel<string | null>({ default: '' })
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
(e: 'create', searchTerm: string): void
|
|
39
|
+
}>()
|
|
40
|
+
defineOptions({ inheritAttrs: false })
|
|
41
|
+
|
|
42
|
+
const attrs = useAttrs() as Record<string, any>
|
|
43
|
+
const attrsWithoutClassStyle = computed(() => {
|
|
44
|
+
return Object.fromEntries(
|
|
45
|
+
Object.entries(attrs).filter(([key]) => key !== 'class' && key !== 'style'),
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const options = createResource({
|
|
50
|
+
url: 'frappe.desk.search.search_link',
|
|
51
|
+
params: {
|
|
52
|
+
doctype: props.doctype,
|
|
53
|
+
txt: '',
|
|
54
|
+
filters: props.filters,
|
|
55
|
+
},
|
|
56
|
+
method: 'POST',
|
|
57
|
+
resourceFetcher: frappeRequest,
|
|
58
|
+
transform: (data: SelectOption[]) => {
|
|
59
|
+
return data.map((doc) => ({
|
|
60
|
+
label: doc.label || doc.value,
|
|
61
|
+
value: doc.value,
|
|
62
|
+
}))
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const createNewOption = {
|
|
67
|
+
type: 'custom' as const,
|
|
68
|
+
key: 'create_new',
|
|
69
|
+
label: 'Create New',
|
|
70
|
+
slotName: 'create-new',
|
|
71
|
+
condition: () => true,
|
|
72
|
+
onClick: ({ searchTerm }) => emit('create', searchTerm),
|
|
73
|
+
} as ComboboxOption
|
|
74
|
+
|
|
75
|
+
const linkOptions = computed(() => {
|
|
76
|
+
if (props.allowCreate) {
|
|
77
|
+
return [...(options.data || []), createNewOption]
|
|
78
|
+
}
|
|
79
|
+
return options.data
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const loadOptions = (txt: string = '') => {
|
|
83
|
+
options.update({
|
|
84
|
+
params: {
|
|
85
|
+
txt,
|
|
86
|
+
doctype: props.doctype,
|
|
87
|
+
filters: props.filters,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
options.reload()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleInputChange = debounce((inputString: string) => {
|
|
94
|
+
loadOptions(inputString || '')
|
|
95
|
+
}, 300)
|
|
96
|
+
|
|
97
|
+
watch([() => props.doctype, () => props.filters], () => loadOptions(''), {
|
|
98
|
+
immediate: true,
|
|
99
|
+
deep: true,
|
|
100
|
+
})
|
|
101
|
+
</script>
|
package/frappe/index.js
CHANGED
package/package.json
CHANGED
|
@@ -309,11 +309,8 @@ const selectedValue = computed({
|
|
|
309
309
|
set(val) {
|
|
310
310
|
query.value = ''
|
|
311
311
|
if (val && !props.multiple) showOptions.value = false
|
|
312
|
-
if (!props.multiple) {
|
|
313
|
-
emit('update:modelValue', val)
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
312
|
emit('update:modelValue', val)
|
|
313
|
+
emit('change', val)
|
|
317
314
|
},
|
|
318
315
|
})
|
|
319
316
|
|
|
@@ -38,6 +38,7 @@ const emit = defineEmits([
|
|
|
38
38
|
'update:selectedOption',
|
|
39
39
|
'focus',
|
|
40
40
|
'blur',
|
|
41
|
+
'input',
|
|
41
42
|
])
|
|
42
43
|
|
|
43
44
|
const searchTerm = ref(getDisplayValue(props.modelValue))
|
|
@@ -238,6 +239,7 @@ const handleInputChange = (event: Event) => {
|
|
|
238
239
|
internalModelValue.value = null
|
|
239
240
|
emit('update:modelValue', null)
|
|
240
241
|
}
|
|
242
|
+
emit('input', searchTerm.value)
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
const handleOpenChange = (open: boolean) => {
|
|
@@ -56,6 +56,7 @@ import TaskItem from '@tiptap/extension-task-item'
|
|
|
56
56
|
import TaskList from '@tiptap/extension-task-list'
|
|
57
57
|
import NamedColorExtension from './extensions/color'
|
|
58
58
|
import NamedHighlightExtension from './extensions/highlight'
|
|
59
|
+
import improvedList from './extensions/list-extension'
|
|
59
60
|
|
|
60
61
|
import { MentionExtension } from './extensions/mention'
|
|
61
62
|
import TextEditorFixedMenu from './TextEditorFixedMenu.vue'
|
|
@@ -165,16 +166,7 @@ onMounted(() => {
|
|
|
165
166
|
}).extend({
|
|
166
167
|
addKeyboardShortcuts() {
|
|
167
168
|
return {
|
|
168
|
-
Backspace: () =>
|
|
169
|
-
const selection = this.editor.view.state.selection
|
|
170
|
-
const { $from } = selection
|
|
171
|
-
if (
|
|
172
|
-
!this.editor.can().liftListItem('listItem') ||
|
|
173
|
-
$from.parentOffset > 0 || !selection.empty
|
|
174
|
-
)
|
|
175
|
-
return false
|
|
176
|
-
return this.editor.commands.liftListItem('listItem')
|
|
177
|
-
},
|
|
169
|
+
Backspace: () => improvedList(this.editor),
|
|
178
170
|
}
|
|
179
171
|
},
|
|
180
172
|
}),
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Selection } from '@tiptap/pm/state'
|
|
2
|
+
import { Editor } from '@tiptap/core'
|
|
3
|
+
|
|
4
|
+
export default function improvedList(editor: Editor) {
|
|
5
|
+
const selection = editor.view.state.selection
|
|
6
|
+
const { $from } = selection
|
|
7
|
+
if ($from.parentOffset > 0 || !selection.empty) return false
|
|
8
|
+
|
|
9
|
+
let nodeBefore = editor.view.state.doc.resolve($from.pos - 1).nodeBefore
|
|
10
|
+
if (!nodeBefore)
|
|
11
|
+
nodeBefore = editor.state.doc.resolve($from.before() - 1).nodeBefore
|
|
12
|
+
console.log(nodeBefore)
|
|
13
|
+
if (nodeBefore && ['bulletList', 'orderedList', 'listItem'].includes(nodeBefore.type.name)) {
|
|
14
|
+
// Join item with the last bullet item
|
|
15
|
+
editor
|
|
16
|
+
.chain()
|
|
17
|
+
.command(({ tr }) => {
|
|
18
|
+
const paragraphContent = $from.parent.content
|
|
19
|
+
const inList = nodeBefore.type.name === 'listItem'
|
|
20
|
+
console.log(inList)
|
|
21
|
+
tr.delete($from.before() - (inList ? 1 : 0), $from.after())
|
|
22
|
+
const insertPos = $from.pos - 4
|
|
23
|
+
tr.insert(insertPos, paragraphContent)
|
|
24
|
+
tr.setSelection(Selection.near(tr.doc.resolve(insertPos)))
|
|
25
|
+
})
|
|
26
|
+
.run()
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (editor.can().liftListItem('listItem'))
|
|
31
|
+
return editor.commands.liftListItem('listItem')
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,6 @@ export * from './components/FormControl'
|
|
|
22
22
|
export { default as FormLabel } from './components/FormLabel.vue'
|
|
23
23
|
export { default as GreenCheckIcon } from './components/GreenCheckIcon.vue'
|
|
24
24
|
export { default as Input } from './components/Input.vue'
|
|
25
|
-
export { default as Link } from './components/Link.vue'
|
|
26
25
|
export { default as ListItem } from './components/ListItem.vue'
|
|
27
26
|
export { default as LoadingIndicator } from './components/LoadingIndicator.vue'
|
|
28
27
|
export { default as LoadingText } from './components/LoadingText.vue'
|
package/src/components/Link.vue
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<component
|
|
3
|
-
:is="isExternal ? 'a' : 'router-link'"
|
|
4
|
-
v-bind="attributes"
|
|
5
|
-
class="cursor-pointer text-blue-500 hover:text-blue-600"
|
|
6
|
-
>
|
|
7
|
-
<slot></slot>
|
|
8
|
-
</component>
|
|
9
|
-
</template>
|
|
10
|
-
|
|
11
|
-
<script>
|
|
12
|
-
export default {
|
|
13
|
-
props: ['to'],
|
|
14
|
-
computed: {
|
|
15
|
-
attributes() {
|
|
16
|
-
return {
|
|
17
|
-
...this.$attrs,
|
|
18
|
-
target: this.isExternal ? '_blank' : null,
|
|
19
|
-
to: !this.isExternal ? this.to : undefined,
|
|
20
|
-
href: this.isExternal ? this.to : undefined,
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
isExternal() {
|
|
24
|
-
return this.to.startsWith('http')
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
</script>
|