frappe-ui 0.1.262 → 0.1.263
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/drive/js/resources.js +2 -3
- package/package.json +1 -1
- package/src/components/Checkbox/Checkbox.vue +1 -1
- package/src/components/ListFilter/ListFilter.vue +1 -1
- package/src/components/ListView/ListGroupHeader.vue +2 -2
- package/src/components/ListView/ListHeader.vue +1 -1
- package/src/components/ListView/ListHeaderItem.vue +1 -1
- package/src/components/ListView/ListRow.vue +2 -2
- package/src/components/ListView/ListRowItem.vue +1 -1
- package/src/components/ListView/ListSelectBanner.vue +4 -4
- package/src/components/Tabs/Tabs.vue +2 -2
- package/src/components/TextEditor/components/MediaNodeView.vue +32 -13
- package/src/components/TextEditor/extensions/image/image-extension.ts +57 -17
- package/src/components/TextEditor/extensions/video-extension.ts +67 -18
- package/src/resources/resources.js +1 -0
|
@@ -68,7 +68,7 @@ export const usersWithAccess = createResource({
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
export const updateAccess = createResource({
|
|
71
|
-
url: 'drive.api.files.
|
|
71
|
+
url: 'drive.api.files.update_access',
|
|
72
72
|
makeParams: (params) => ({ ...params, method: params.method || 'share' }),
|
|
73
73
|
onError: (error) => toast.error(error.messages[0]),
|
|
74
74
|
})
|
|
@@ -96,11 +96,10 @@ export const getTeam = createResource({
|
|
|
96
96
|
})
|
|
97
97
|
|
|
98
98
|
export const rename = createResource({
|
|
99
|
-
url: 'drive.api.files.
|
|
99
|
+
url: 'drive.api.files.rename',
|
|
100
100
|
method: 'POST',
|
|
101
101
|
makeParams: (data) => {
|
|
102
102
|
return {
|
|
103
|
-
method: 'rename',
|
|
104
103
|
...data,
|
|
105
104
|
}
|
|
106
105
|
},
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="flex items-center">
|
|
3
3
|
<button
|
|
4
4
|
@click="toggleGroup"
|
|
5
|
-
class="
|
|
5
|
+
class="ms-[3px] me-[11px] rounded p-1 hover:bg-surface-gray-2"
|
|
6
6
|
>
|
|
7
7
|
<DownSolid
|
|
8
8
|
class="h-4 w-4 text-ink-gray-6 transition-transform duration-200"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
</button>
|
|
12
12
|
<slot>
|
|
13
|
-
<div class="w-full py-1.5
|
|
13
|
+
<div class="w-full py-1.5 pe-2">
|
|
14
14
|
<component
|
|
15
15
|
v-if="list.slots['group-header']"
|
|
16
16
|
:is="list.slots['group-header']"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:class="item.align ? alignmentMap[item.align] : 'justify-between'"
|
|
6
6
|
>
|
|
7
7
|
<div
|
|
8
|
-
class="flex items-center
|
|
8
|
+
class="flex items-center gap-2 truncate text-sm text-ink-gray-5"
|
|
9
9
|
:class="$attrs.class"
|
|
10
10
|
>
|
|
11
11
|
<slot name="prefix" v-bind="{ item }" />
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
class="[all:unset] hover:[all:unset]"
|
|
23
23
|
>
|
|
24
24
|
<div
|
|
25
|
-
class="grid items-center
|
|
25
|
+
class="grid items-center gap-4 px-2"
|
|
26
26
|
:style="{
|
|
27
27
|
height: rowHeight,
|
|
28
28
|
gridTemplateColumns: getGridTemplateColumns(
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
>
|
|
34
34
|
<div
|
|
35
35
|
v-if="list.options.selectable"
|
|
36
|
-
class="w-fit
|
|
36
|
+
class="w-fit pe-2 py-3 flex"
|
|
37
37
|
@click.stop.prevent
|
|
38
38
|
@dblclick.stop
|
|
39
39
|
>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
class="absolute inset-x-0 bottom-6 mx-auto w-max text-base"
|
|
13
13
|
>
|
|
14
14
|
<div
|
|
15
|
-
class="flex min-w-[596px] items-center
|
|
15
|
+
class="flex min-w-[596px] items-center gap-3 rounded-lg bg-surface-white px-4 py-2 shadow-2xl"
|
|
16
16
|
:class="$attrs.class"
|
|
17
17
|
>
|
|
18
18
|
<slot
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<div
|
|
27
27
|
class="flex flex-1 justify-between border-r border-outline-gray-2 text-ink-gray-9"
|
|
28
28
|
>
|
|
29
|
-
<div class="flex items-center
|
|
29
|
+
<div class="flex items-center gap-3">
|
|
30
30
|
<Checkbox
|
|
31
31
|
:modelValue="true"
|
|
32
32
|
:disabled="true"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
/>
|
|
35
35
|
<div>{{ selectedText }}</div>
|
|
36
36
|
</div>
|
|
37
|
-
<div class="
|
|
37
|
+
<div class="me-3">
|
|
38
38
|
<slot
|
|
39
39
|
name="actions"
|
|
40
40
|
v-bind="{
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
/>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
|
-
<div class="flex items-center
|
|
49
|
+
<div class="flex items-center gap-1">
|
|
50
50
|
<Button
|
|
51
51
|
class="w- text-ink-gray-7"
|
|
52
52
|
:disabled="list.allRowsSelected"
|
|
@@ -16,7 +16,7 @@ const model = defineModel<string | number>({ default: 0 })
|
|
|
16
16
|
const indicatorXCss = `left-0 bottom-0 h-[2px] w-[--reka-tabs-indicator-size] transition-[width,transform]
|
|
17
17
|
translate-x-[--reka-tabs-indicator-position] translate-y-[1px]`
|
|
18
18
|
|
|
19
|
-
const indicatorYCss = `
|
|
19
|
+
const indicatorYCss = `end-0 top-0 w-[2px] h-[--reka-tabs-indicator-size]
|
|
20
20
|
translate-y-[--reka-tabs-indicator-position] transition-[height,transform]`
|
|
21
21
|
|
|
22
22
|
// Using a plain <button> element via `h('button')` to avoid picking up
|
|
@@ -41,7 +41,7 @@ defineSlots<{
|
|
|
41
41
|
v-model="model"
|
|
42
42
|
>
|
|
43
43
|
<TabsList
|
|
44
|
-
class="relative min-h-fit flex data-[orientation=vertical]:flex-col p-1 border-b data-[orientation=vertical]:border-
|
|
44
|
+
class="relative min-h-fit flex data-[orientation=vertical]:flex-col p-1 border-b data-[orientation=vertical]:border-e gap-5"
|
|
45
45
|
:class="{
|
|
46
46
|
'overflow-x-auto overflow-y-hidden px-5': !props.vertical,
|
|
47
47
|
'py-3': props.vertical,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
2
|
+
import { ref, onMounted, onUnmounted, computed, h } from 'vue'
|
|
3
3
|
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
|
4
4
|
import LoadingIndicator from '../../LoadingIndicator.vue'
|
|
5
5
|
import Tooltip from '../../Tooltip/Tooltip.vue'
|
|
6
|
+
import { localFileMap } from '../extensions/image/image-extension'
|
|
6
7
|
import { ErrorMessage } from '../../ErrorMessage'
|
|
7
8
|
import LucideAlignLeft from '~icons/lucide/align-left'
|
|
8
9
|
import LucideAlignCenter from '~icons/lucide/align-center'
|
|
@@ -13,6 +14,7 @@ import LucideFloatRight from '~icons/lucide/align-horizontal-justify-end'
|
|
|
13
14
|
import LucideNoFloat from '~icons/lucide/align-vertical-space-around'
|
|
14
15
|
import LucideCaptions from '~icons/lucide/captions'
|
|
15
16
|
import LucideMoveDiagonal2 from '~icons/lucide/move-diagonal-2'
|
|
17
|
+
import LucideRotateCw from '~icons/lucide/rotate-cw'
|
|
16
18
|
|
|
17
19
|
const props = defineProps(nodeViewProps)
|
|
18
20
|
|
|
@@ -30,6 +32,8 @@ const floatButtonRef = ref<HTMLElement | null>(null)
|
|
|
30
32
|
|
|
31
33
|
const showCaption = ref(props.node.attrs.alt ? true : false)
|
|
32
34
|
const isVideo = computed(() => props.node.type.name === 'video')
|
|
35
|
+
const isUploaded = computed(() => Boolean(props.node.attrs.src))
|
|
36
|
+
const fileContent = computed(() => localFileMap.get(props.node.attrs.uploadId)?.b64)
|
|
33
37
|
|
|
34
38
|
const currentAlignIcon = computed(() => {
|
|
35
39
|
return (
|
|
@@ -273,14 +277,16 @@ const wrapperClasses = (float: string) => [
|
|
|
273
277
|
]" :style="{
|
|
274
278
|
width: node.attrs.width ? `${node.attrs.width}px` : 'auto',
|
|
275
279
|
}">
|
|
276
|
-
<div v-if="
|
|
277
|
-
<img v-if="!isVideo" ref="mediaRef" class="rounded-[2px]" :
|
|
278
|
-
:width="node.attrs.width" :height="node.attrs.height"
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
<div v-if="isUploaded || fileContent" class="relative">
|
|
281
|
+
<img v-if="!isVideo" ref="mediaRef" class="rounded-[2px]" :class="!isUploaded && 'opacity-40'" :src="node.attrs.src || fileContent"
|
|
282
|
+
:alt="node.attrs.alt || ''" :width="node.attrs.width" :height="node.attrs.height"
|
|
283
|
+
@click.stop="selectMedia" @load="handleMediaLoaded" />
|
|
284
|
+
<video v-else ref="mediaRef" class="rounded-[2px]" :class="!isUploaded && 'opacity-40'" :src="node.attrs.src || fileContent"
|
|
285
|
+
:width="node.attrs.width" :height="node.attrs.height" :autoplay="node.attrs.autoplay"
|
|
286
|
+
:loop="node.attrs.loop" :muted="node.attrs.muted" :controls="isUploaded" @click.stop="selectMedia"
|
|
287
|
+
@loadedmetadata="handleMediaLoaded" />
|
|
288
|
+
|
|
289
|
+
<div v-if="isUploaded" class="absolute top-2 right-2 items-center bg-black/65 px-1.5 py-1 gap-2 rounded group-hover:flex"
|
|
284
290
|
:class="selected && isEditable ? 'flex' : 'hidden'">
|
|
285
291
|
<button>
|
|
286
292
|
<LucideCaptions @click="toggleCaptions" class="size-4"
|
|
@@ -350,7 +356,16 @@ const wrapperClasses = (float: string) => [
|
|
|
350
356
|
</div>
|
|
351
357
|
</div>
|
|
352
358
|
|
|
353
|
-
<
|
|
359
|
+
<Button
|
|
360
|
+
v-else
|
|
361
|
+
variant="solid"
|
|
362
|
+
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
363
|
+
:icon-left="h(LucideRotateCw, { class: 'size-4' })"
|
|
364
|
+
label="Try again"
|
|
365
|
+
@click="isVideo ? editor.commands.reuploadVideo(node.attrs.uploadId) : editor.commands.reuploadImage(node.attrs.uploadId)"
|
|
366
|
+
/>
|
|
367
|
+
|
|
368
|
+
<button v-if="selected && isEditable && isUploaded" class="absolute bottom-2 right-2 cursor-nw-resize bg-black/65 rounded p-1"
|
|
354
369
|
@mousedown.prevent="startResize">
|
|
355
370
|
<LucideMoveDiagonal2 class="text-white size-4" />
|
|
356
371
|
</button>
|
|
@@ -364,14 +379,18 @@ const wrapperClasses = (float: string) => [
|
|
|
364
379
|
</div>
|
|
365
380
|
</div>
|
|
366
381
|
</div>
|
|
382
|
+
<div v-else class="flex flex-col items-center justify-center gap-2 border rounded text-ink-gray-6 text-sm py-5 max-w-full" :class="{ 'border-none': selected }" :style="{ width: node.attrs.width + 'px', aspectRatio: node.attrs.width && node.attrs.height ? `${node.attrs.width} / ${node.attrs.height}` : undefined,}">
|
|
383
|
+
<div class="text-ink-gray-8 text-base">This {{ isVideo ? 'video' : 'image' }} hasn't yet been uploaded.</div>
|
|
384
|
+
<div v-if="node.attrs.error" class="text-sm text-ink-red-4">Upload failed: {{ node.attrs.error }}</div>
|
|
385
|
+
</div>
|
|
367
386
|
|
|
368
387
|
<input v-if="(node.attrs.alt || showCaption) && !node.attrs.error" v-model="caption"
|
|
369
388
|
class="w-full text-center bg-transparent text-sm text-ink-gray-6 h-7 border-none focus:ring-0 placeholder-ink-gray-4"
|
|
370
389
|
placeholder="Add caption" :disabled="!isEditable" @change="updateCaption" @keydown="handleKeydown" />
|
|
371
390
|
|
|
372
|
-
<div v-if="node.attrs.error" class="w-full py-1.5">
|
|
373
|
-
<ErrorMessage :message="`Upload
|
|
391
|
+
<div v-if="node.attrs.error && fileContent" class="w-full py-1.5 text-center">
|
|
392
|
+
<ErrorMessage :message="`Upload failed: ${node.attrs.error}`" />
|
|
374
393
|
</div>
|
|
375
394
|
</div>
|
|
376
395
|
</NodeViewWrapper>
|
|
377
|
-
</template>
|
|
396
|
+
</template>
|
|
@@ -11,6 +11,8 @@ import { Node } from '@tiptap/pm/model'
|
|
|
11
11
|
import { fileToBase64 } from '../../../../index'
|
|
12
12
|
import { UploadedFile } from '../../../../utils/useFileUpload'
|
|
13
13
|
|
|
14
|
+
export const localFileMap = new Map()
|
|
15
|
+
|
|
14
16
|
export interface ImageExtensionOptions {
|
|
15
17
|
/**
|
|
16
18
|
* Function to handle image uploads
|
|
@@ -58,6 +60,10 @@ declare module '@tiptap/core' {
|
|
|
58
60
|
* Set image float for text wrapping
|
|
59
61
|
*/
|
|
60
62
|
setImageFloat: (float: 'left' | 'right' | null) => ReturnType
|
|
63
|
+
/**
|
|
64
|
+
* Re-upload a failed image using the file stored in localFileMap
|
|
65
|
+
*/
|
|
66
|
+
reuploadImage: (uploadId: string) => ReturnType
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
}
|
|
@@ -218,6 +224,45 @@ export const ImageExtension = NodeExtension.create<ImageExtensionOptions>({
|
|
|
218
224
|
input.click()
|
|
219
225
|
return true
|
|
220
226
|
},
|
|
227
|
+
|
|
228
|
+
reuploadImage:
|
|
229
|
+
(uploadId: string) =>
|
|
230
|
+
({ editor }) => {
|
|
231
|
+
const fileData = localFileMap.get(uploadId)
|
|
232
|
+
if (!fileData) {
|
|
233
|
+
console.error('reuploadImage: no file with uploadId', uploadId)
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Find the node position
|
|
238
|
+
let nodePos: number | null = null
|
|
239
|
+
editor.view.state.doc.descendants((node, pos) => {
|
|
240
|
+
if (
|
|
241
|
+
node.type.name === 'image' &&
|
|
242
|
+
node.attrs.uploadId === uploadId
|
|
243
|
+
) {
|
|
244
|
+
nodePos = pos
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if (nodePos === null) {
|
|
250
|
+
console.error(
|
|
251
|
+
'reuploadImage: could not find node with uploadId',
|
|
252
|
+
uploadId,
|
|
253
|
+
)
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Re-run the upload using the stored file, replacing the node at its position
|
|
258
|
+
return uploadImageBase(
|
|
259
|
+
fileData.file,
|
|
260
|
+
editor.view,
|
|
261
|
+
nodePos,
|
|
262
|
+
this.options,
|
|
263
|
+
'replace',
|
|
264
|
+
)
|
|
265
|
+
},
|
|
221
266
|
}
|
|
222
267
|
},
|
|
223
268
|
|
|
@@ -369,6 +414,7 @@ function findInsertPosition(
|
|
|
369
414
|
}
|
|
370
415
|
|
|
371
416
|
// Base upload function shared by all image upload methods
|
|
417
|
+
type ImageDimensions = { width: number | null; height: number | null }
|
|
372
418
|
function uploadImageBase(
|
|
373
419
|
file: File,
|
|
374
420
|
view: EditorView,
|
|
@@ -387,10 +433,19 @@ function uploadImageBase(
|
|
|
387
433
|
|
|
388
434
|
fileToBase64(file)
|
|
389
435
|
.then((base64Result: string) => {
|
|
436
|
+
localFileMap.set(uploadId, { b64: base64Result, file })
|
|
437
|
+
|
|
438
|
+
return getImageDimensions(base64Result)
|
|
439
|
+
.catch(() => ({ width: null, height: null }))
|
|
440
|
+
.then((dimensions) => dimensions)
|
|
441
|
+
})
|
|
442
|
+
.then((dimensions: ImageDimensions) => {
|
|
390
443
|
const node = view.state.schema.nodes.image.create({
|
|
391
444
|
loading: true,
|
|
392
445
|
uploadId,
|
|
393
|
-
src:
|
|
446
|
+
src: null,
|
|
447
|
+
width: dimensions.width,
|
|
448
|
+
height: dimensions.height,
|
|
394
449
|
})
|
|
395
450
|
|
|
396
451
|
const tr = view.state.tr
|
|
@@ -438,22 +493,6 @@ function uploadImageBase(
|
|
|
438
493
|
|
|
439
494
|
return options.uploadFunction(file)
|
|
440
495
|
})
|
|
441
|
-
.then((uploadedImage: UploadedFile) => {
|
|
442
|
-
return getImageDimensions(uploadedImage.file_url)
|
|
443
|
-
.then((dimensions) => {
|
|
444
|
-
return {
|
|
445
|
-
...uploadedImage,
|
|
446
|
-
width: dimensions.width,
|
|
447
|
-
height: dimensions.height,
|
|
448
|
-
} as UploadedFile & { width: number; height: number }
|
|
449
|
-
})
|
|
450
|
-
.catch(() => {
|
|
451
|
-
return uploadedImage as UploadedFile & {
|
|
452
|
-
width: number
|
|
453
|
-
height: number
|
|
454
|
-
}
|
|
455
|
-
})
|
|
456
|
-
})
|
|
457
496
|
.then((uploadedImage) => {
|
|
458
497
|
const transaction = view.state.tr
|
|
459
498
|
|
|
@@ -466,6 +505,7 @@ function uploadImageBase(
|
|
|
466
505
|
height: uploadedImage.height || node.attrs.height,
|
|
467
506
|
loading: false,
|
|
468
507
|
})
|
|
508
|
+
localFileMap.delete(node.attrs.uploadId)
|
|
469
509
|
return false
|
|
470
510
|
}
|
|
471
511
|
})
|
|
@@ -10,6 +10,7 @@ import { EditorView } from '@tiptap/pm/view'
|
|
|
10
10
|
import { Node } from '@tiptap/pm/model'
|
|
11
11
|
import { fileToBase64 } from '../../../index'
|
|
12
12
|
import { UploadedFile } from '../../../utils/useFileUpload'
|
|
13
|
+
import { localFileMap } from './image/image-extension'
|
|
13
14
|
|
|
14
15
|
export interface VideoExtensionOptions {
|
|
15
16
|
/**
|
|
@@ -58,6 +59,11 @@ declare module '@tiptap/core' {
|
|
|
58
59
|
* Set video floating
|
|
59
60
|
*/
|
|
60
61
|
setVideoFloat: (float: 'left' | 'right' | null) => ReturnType
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Re-upload a failed video using the file stored in localFileMap
|
|
65
|
+
*/
|
|
66
|
+
reuploadVideo: (uploadId: string) => ReturnType
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
}
|
|
@@ -206,6 +212,48 @@ export const VideoExtension = NodeExtension.create<VideoExtensionOptions>({
|
|
|
206
212
|
input.click()
|
|
207
213
|
return true
|
|
208
214
|
},
|
|
215
|
+
|
|
216
|
+
reuploadVideo:
|
|
217
|
+
(uploadId: string) =>
|
|
218
|
+
({ editor }) => {
|
|
219
|
+
const fileData = localFileMap.get(uploadId)
|
|
220
|
+
if (!fileData) {
|
|
221
|
+
console.error(
|
|
222
|
+
'reuploadVideo: no file found in localFileMap for uploadId',
|
|
223
|
+
uploadId,
|
|
224
|
+
)
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Find the node position
|
|
229
|
+
let nodePos: number | null = null
|
|
230
|
+
editor.view.state.doc.descendants((node, pos) => {
|
|
231
|
+
if (
|
|
232
|
+
node.type.name === 'video' &&
|
|
233
|
+
node.attrs.uploadId === uploadId
|
|
234
|
+
) {
|
|
235
|
+
nodePos = pos
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (nodePos === null) {
|
|
241
|
+
console.error(
|
|
242
|
+
'reuploadVideo: could not find node with uploadId',
|
|
243
|
+
uploadId,
|
|
244
|
+
)
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Re-run the upload using the stored file, replacing the node at its position
|
|
249
|
+
return uploadVideoBase(
|
|
250
|
+
fileData.file,
|
|
251
|
+
editor.view,
|
|
252
|
+
nodePos,
|
|
253
|
+
this.options,
|
|
254
|
+
'replace',
|
|
255
|
+
)
|
|
256
|
+
},
|
|
209
257
|
}
|
|
210
258
|
},
|
|
211
259
|
|
|
@@ -353,6 +401,8 @@ function findInsertPosition(
|
|
|
353
401
|
}
|
|
354
402
|
|
|
355
403
|
// Base upload function shared by all video upload methods
|
|
404
|
+
type VideoDimensions = { width: number | null; height: number | null }
|
|
405
|
+
|
|
356
406
|
function uploadVideoBase(
|
|
357
407
|
file: File,
|
|
358
408
|
view: EditorView,
|
|
@@ -371,10 +421,23 @@ function uploadVideoBase(
|
|
|
371
421
|
|
|
372
422
|
fileToBase64(file)
|
|
373
423
|
.then((base64Result: string) => {
|
|
424
|
+
localFileMap.set(uploadId, { b64: base64Result, file })
|
|
425
|
+
|
|
426
|
+
const objectUrl = URL.createObjectURL(file)
|
|
427
|
+
return getVideoDimensions(objectUrl)
|
|
428
|
+
.catch((): VideoDimensions => ({ width: null, height: null }))
|
|
429
|
+
.then((dimensions: VideoDimensions) => {
|
|
430
|
+
URL.revokeObjectURL(objectUrl)
|
|
431
|
+
return dimensions
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
.then((dimensions: VideoDimensions) => {
|
|
374
435
|
const node = view.state.schema.nodes.video.create({
|
|
375
436
|
loading: true,
|
|
376
437
|
uploadId,
|
|
377
|
-
src:
|
|
438
|
+
src: null,
|
|
439
|
+
width: dimensions.width,
|
|
440
|
+
height: dimensions.height,
|
|
378
441
|
})
|
|
379
442
|
|
|
380
443
|
const tr = view.state.tr
|
|
@@ -422,22 +485,6 @@ function uploadVideoBase(
|
|
|
422
485
|
|
|
423
486
|
return options.uploadFunction(file)
|
|
424
487
|
})
|
|
425
|
-
.then((uploadedVideo: UploadedFile) => {
|
|
426
|
-
return getVideoDimensions(uploadedVideo.file_url)
|
|
427
|
-
.then((dimensions) => {
|
|
428
|
-
return {
|
|
429
|
-
...uploadedVideo,
|
|
430
|
-
width: dimensions.width,
|
|
431
|
-
height: dimensions.height,
|
|
432
|
-
} as UploadedFile & { width: number; height: number }
|
|
433
|
-
})
|
|
434
|
-
.catch(() => {
|
|
435
|
-
return uploadedVideo as UploadedFile & {
|
|
436
|
-
width: number
|
|
437
|
-
height: number
|
|
438
|
-
}
|
|
439
|
-
})
|
|
440
|
-
})
|
|
441
488
|
.then((uploadedVideo) => {
|
|
442
489
|
const transaction = view.state.tr
|
|
443
490
|
|
|
@@ -450,6 +497,7 @@ function uploadVideoBase(
|
|
|
450
497
|
height: uploadedVideo.height || node.attrs.height,
|
|
451
498
|
loading: false,
|
|
452
499
|
})
|
|
500
|
+
localFileMap.delete(node.attrs.uploadId)
|
|
453
501
|
return false
|
|
454
502
|
}
|
|
455
503
|
})
|
|
@@ -466,6 +514,7 @@ function uploadVideoBase(
|
|
|
466
514
|
|
|
467
515
|
view.state.doc.descendants((node, pos) => {
|
|
468
516
|
if (node.type.name === 'video' && node.attrs.uploadId === uploadId) {
|
|
517
|
+
// width/height are preserved from ...node.attrs (pre-fetched from local file)
|
|
469
518
|
transaction.setNodeMarkup(pos, undefined, {
|
|
470
519
|
...node.attrs,
|
|
471
520
|
loading: false,
|
|
@@ -541,7 +590,7 @@ function updateNodeWithDimensions(
|
|
|
541
590
|
}
|
|
542
591
|
})
|
|
543
592
|
.catch((error) => {
|
|
544
|
-
console.error('Could not
|
|
593
|
+
console.error('Could not get video dimensions', error)
|
|
545
594
|
})
|
|
546
595
|
}
|
|
547
596
|
|