frappe-ui 0.1.166 → 0.1.168
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 +3 -2
- package/src/components/Combobox/Combobox.vue +16 -1
- package/src/components/TextEditor/InsertVideo.vue +8 -91
- package/src/components/TextEditor/TextEditor.vue +3 -2
- package/src/components/TextEditor/extensions/{markdown-paste-extension.ts → content-paste-extension.ts} +27 -5
- package/src/components/TextEditor/extensions/image/image-extension.ts +1 -1
- package/src/index.ts +1 -0
- package/src/utils/fileUploadHandler.ts +1 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.168",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"test": "vitest --run",
|
|
9
9
|
"type-check": "tsc --noEmit",
|
|
10
10
|
"prettier": "yarn prettier -w ./src",
|
|
11
|
-
"bump-and-release": "yarn test && git pull --rebase origin main && yarn
|
|
11
|
+
"bump-and-release": "yarn test && git pull --rebase origin main && yarn run release-patch",
|
|
12
|
+
"release-patch": "yarn version --patch && git push && git push --tags",
|
|
12
13
|
"dev": "vite",
|
|
13
14
|
"build": "vite build",
|
|
14
15
|
"preview": "vite preview",
|
|
@@ -44,7 +44,12 @@ interface ComboboxProps {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const props = defineProps<ComboboxProps>()
|
|
47
|
-
const emit = defineEmits([
|
|
47
|
+
const emit = defineEmits([
|
|
48
|
+
'update:modelValue',
|
|
49
|
+
'update:selectedOption',
|
|
50
|
+
'focus',
|
|
51
|
+
'blur',
|
|
52
|
+
])
|
|
48
53
|
|
|
49
54
|
const searchTerm = ref(getDisplayValue(props.modelValue))
|
|
50
55
|
const internalModelValue = ref(props.modelValue)
|
|
@@ -196,6 +201,14 @@ const handleOpenChange = (open: boolean) => {
|
|
|
196
201
|
userHasTyped.value = false
|
|
197
202
|
}
|
|
198
203
|
}
|
|
204
|
+
|
|
205
|
+
const handleFocus = (event: FocusEvent) => {
|
|
206
|
+
emit('focus', event)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const handleBlur = (event: FocusEvent) => {
|
|
210
|
+
emit('blur', event)
|
|
211
|
+
}
|
|
199
212
|
</script>
|
|
200
213
|
|
|
201
214
|
<template>
|
|
@@ -215,6 +228,8 @@ const handleOpenChange = (open: boolean) => {
|
|
|
215
228
|
<ComboboxInput
|
|
216
229
|
:value="searchTerm"
|
|
217
230
|
@input="handleInputChange"
|
|
231
|
+
@focus="handleFocus"
|
|
232
|
+
@blur="handleBlur"
|
|
218
233
|
class="bg-transparent p-0 focus:outline-0 border-0 focus:border-0 focus:ring-0 text-base text-ink-gray-8 h-full placeholder:text-ink-gray-4 w-full"
|
|
219
234
|
:placeholder="placeholder || ''"
|
|
220
235
|
:disabled="disabled"
|
|
@@ -1,97 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<slot v-bind="{ onClick:
|
|
3
|
-
<Dialog
|
|
4
|
-
:options="{ title: 'Add Video' }"
|
|
5
|
-
v-model="addVideoDialog.show"
|
|
6
|
-
@after-leave="reset"
|
|
7
|
-
>
|
|
8
|
-
<template #body-content>
|
|
9
|
-
<FileUploader
|
|
10
|
-
file-types="video/*"
|
|
11
|
-
@success="(file) => (addVideoDialog.url = file.file_url)"
|
|
12
|
-
>
|
|
13
|
-
<template v-slot="{ file, progress, uploading, openFileSelector }">
|
|
14
|
-
<div class="flex items-center space-x-2">
|
|
15
|
-
<Button @click="openFileSelector">
|
|
16
|
-
{{
|
|
17
|
-
uploading
|
|
18
|
-
? `Uploading ${progress}%`
|
|
19
|
-
: addVideoDialog.url
|
|
20
|
-
? 'Change Video'
|
|
21
|
-
: 'Upload Video'
|
|
22
|
-
}}
|
|
23
|
-
</Button>
|
|
24
|
-
<Button
|
|
25
|
-
v-if="addVideoDialog.url"
|
|
26
|
-
@click="
|
|
27
|
-
() => {
|
|
28
|
-
addVideoDialog.url = null
|
|
29
|
-
addVideoDialog.file = null
|
|
30
|
-
}
|
|
31
|
-
"
|
|
32
|
-
>
|
|
33
|
-
Remove
|
|
34
|
-
</Button>
|
|
35
|
-
</div>
|
|
36
|
-
</template>
|
|
37
|
-
</FileUploader>
|
|
38
|
-
<video
|
|
39
|
-
v-if="addVideoDialog.url"
|
|
40
|
-
:src="addVideoDialog.url"
|
|
41
|
-
class="mt-2 w-full rounded-lg"
|
|
42
|
-
type="video/mp4"
|
|
43
|
-
controls
|
|
44
|
-
/>
|
|
45
|
-
</template>
|
|
46
|
-
<template #actions>
|
|
47
|
-
<div class="flex gap-2">
|
|
48
|
-
<Button variant="solid" @click="addVideo(addVideoDialog.url)">
|
|
49
|
-
Insert Video
|
|
50
|
-
</Button>
|
|
51
|
-
<Button @click="reset">Cancel</Button>
|
|
52
|
-
</div>
|
|
53
|
-
</template>
|
|
54
|
-
</Dialog>
|
|
2
|
+
<slot v-bind="{ onClick: selectAndUploadVideo }"></slot>
|
|
55
3
|
</template>
|
|
56
|
-
<script>
|
|
57
|
-
import {
|
|
58
|
-
import { Dialog } from '../Dialog'
|
|
59
|
-
import { FileUploader } from '../FileUploader'
|
|
4
|
+
<script setup lang="ts">
|
|
5
|
+
import { Editor } from '@tiptap/vue-3'
|
|
60
6
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expose: ['openDialog'],
|
|
65
|
-
data() {
|
|
66
|
-
return {
|
|
67
|
-
addVideoDialog: { url: '', file: null, show: false },
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
components: { Button, Dialog, FileUploader },
|
|
71
|
-
methods: {
|
|
72
|
-
openDialog() {
|
|
73
|
-
this.addVideoDialog.show = true
|
|
74
|
-
},
|
|
75
|
-
onVideoSelect(e) {
|
|
76
|
-
let file = e.target.files[0]
|
|
77
|
-
if (!file) {
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
this.addVideoDialog.file = file
|
|
81
|
-
},
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
editor: Editor
|
|
9
|
+
}>()
|
|
82
10
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.editor
|
|
86
|
-
.chain()
|
|
87
|
-
.focus()
|
|
88
|
-
.insertContent(`<video src="${src}"></video>`)
|
|
89
|
-
.run()
|
|
90
|
-
this.reset()
|
|
91
|
-
},
|
|
92
|
-
reset() {
|
|
93
|
-
this.addVideoDialog = this.$options.data().addVideoDialog
|
|
94
|
-
},
|
|
95
|
-
},
|
|
11
|
+
function selectAndUploadVideo() {
|
|
12
|
+
props.editor.chain().focus().selectAndUploadVideo().run()
|
|
96
13
|
}
|
|
97
14
|
</script>
|
|
@@ -59,7 +59,7 @@ import TextEditorBubbleMenu from './TextEditorBubbleMenu.vue'
|
|
|
59
59
|
import TextEditorFloatingMenu from './TextEditorFloatingMenu.vue'
|
|
60
60
|
import EmojiExtension from './extensions/emoji/emoji-extension'
|
|
61
61
|
import SlashCommands from './extensions/slash-commands/slash-commands-extension'
|
|
62
|
-
import {
|
|
62
|
+
import { ContentPasteExtension } from './extensions/content-paste-extension'
|
|
63
63
|
import { TagNode, TagExtension } from './extensions/tag/tag-extension'
|
|
64
64
|
import { Heading } from './extensions/heading/heading'
|
|
65
65
|
import { ImageGroup } from './extensions/image-group/image-group-extension'
|
|
@@ -202,9 +202,10 @@ onMounted(() => {
|
|
|
202
202
|
TagExtension.configure({
|
|
203
203
|
tags: () => props.tags,
|
|
204
204
|
}),
|
|
205
|
-
|
|
205
|
+
ContentPasteExtension.configure({
|
|
206
206
|
enabled: true,
|
|
207
207
|
showConfirmation: true,
|
|
208
|
+
uploadFunction: props.uploadFunction || defaultUploadFunction,
|
|
208
209
|
}),
|
|
209
210
|
...(props.extensions || []),
|
|
210
211
|
],
|
|
@@ -1,31 +1,53 @@
|
|
|
1
1
|
import { Extension } from '@tiptap/core'
|
|
2
2
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
3
3
|
import { DOMParser } from '@tiptap/pm/model'
|
|
4
|
+
import { EditorView } from 'prosemirror-view'
|
|
4
5
|
import { detectMarkdown, markdownToHTML } from '../../../utils/markdown'
|
|
6
|
+
import { processMultipleImages } from './image/image-extension'
|
|
5
7
|
|
|
6
|
-
export interface
|
|
8
|
+
export interface ContentPasteOptions {
|
|
7
9
|
enabled: boolean
|
|
8
10
|
showConfirmation: boolean
|
|
11
|
+
uploadFunction: Function | null
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export const
|
|
12
|
-
name: '
|
|
14
|
+
export const ContentPasteExtension = Extension.create<ContentPasteOptions>({
|
|
15
|
+
name: 'contentPaste',
|
|
13
16
|
|
|
14
17
|
addOptions() {
|
|
15
18
|
return {
|
|
16
19
|
enabled: true,
|
|
17
20
|
showConfirmation: true,
|
|
21
|
+
uploadFunction: null,
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
|
|
21
25
|
addProseMirrorPlugins() {
|
|
26
|
+
const extensionThis = this
|
|
22
27
|
return [
|
|
23
28
|
new Plugin({
|
|
24
|
-
key: new PluginKey('
|
|
29
|
+
key: new PluginKey('contentPaste'),
|
|
25
30
|
props: {
|
|
26
|
-
handlePaste: (
|
|
31
|
+
handlePaste: (
|
|
32
|
+
view: EditorView,
|
|
33
|
+
event: ClipboardEvent,
|
|
34
|
+
slice: any,
|
|
35
|
+
) => {
|
|
27
36
|
if (!this.options.enabled) return false
|
|
28
37
|
|
|
38
|
+
// handle image pasting
|
|
39
|
+
const files: File[] | [] = Array.from(
|
|
40
|
+
event.clipboardData?.files || [],
|
|
41
|
+
)
|
|
42
|
+
const images = Array.from(files).filter((file) =>
|
|
43
|
+
file.type.startsWith('image/'),
|
|
44
|
+
)
|
|
45
|
+
if (images.length > 0) {
|
|
46
|
+
processMultipleImages(images, view, null, extensionThis.options)
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// handle markdown pasting
|
|
29
51
|
const text = event.clipboardData?.getData('text/plain')
|
|
30
52
|
if (!text) return false
|
|
31
53
|
|
package/src/index.ts
CHANGED
|
@@ -90,6 +90,7 @@ export { default as fileToBase64 } from './utils/file-to-base64'
|
|
|
90
90
|
export { default as FileUploadHandler } from './utils/fileUploadHandler'
|
|
91
91
|
export { usePageMeta } from './utils/pageMeta'
|
|
92
92
|
export { dayjsLocal, dayjs } from './utils/dayjs'
|
|
93
|
+
export * from './utils/useFileUpload'
|
|
93
94
|
|
|
94
95
|
// data-fetching, resources
|
|
95
96
|
export {
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
private?: boolean
|
|
3
|
-
folder?: string
|
|
4
|
-
file_url?: string
|
|
5
|
-
doctype?: string
|
|
6
|
-
docname?: string
|
|
7
|
-
fieldname?: string
|
|
8
|
-
method?: string
|
|
9
|
-
type?: string
|
|
10
|
-
upload_endpoint?: string
|
|
11
|
-
optimize?: boolean
|
|
12
|
-
max_width?: number
|
|
13
|
-
max_height?: number
|
|
14
|
-
}
|
|
1
|
+
import { UploadOptions } from "./useFileUpload"
|
|
15
2
|
|
|
16
3
|
type EventListenerOption = 'start' | 'progress' | 'finish' | 'error'
|
|
17
4
|
|