feat: Native video display (#5866)
This commit is contained in:
@@ -1,23 +1,29 @@
|
||||
import * as Sentry from "@sentry/react";
|
||||
import invariant from "invariant";
|
||||
import { NodeSelection } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import FileHelper from "../lib/FileHelper";
|
||||
import uploadPlaceholderPlugin, {
|
||||
findPlaceholder,
|
||||
} from "../lib/uploadPlaceholder";
|
||||
import findAttachmentById from "../queries/findAttachmentById";
|
||||
|
||||
export type Options = {
|
||||
/** Dictionary object containing translation strings */
|
||||
dictionary: any;
|
||||
/** Set to true to force images to become attachments */
|
||||
/** Set to true to force images and videos to become file attachments */
|
||||
isAttachment?: boolean;
|
||||
/** Set to true to replace any existing image at the users selection */
|
||||
replaceExisting?: boolean;
|
||||
/** Callback fired to upload a file */
|
||||
uploadFile?: (file: File) => Promise<string>;
|
||||
/** Callback fired when the user starts a file upload */
|
||||
onFileUploadStart?: () => void;
|
||||
/** Callback fired when the user completes a file upload */
|
||||
onFileUploadStop?: () => void;
|
||||
/** Callback fired when a toast needs to be displayed */
|
||||
onShowToast: (message: string) => void;
|
||||
/** Attributes to overwrite */
|
||||
attrs?: {
|
||||
/** Width to use when inserting image */
|
||||
width?: number;
|
||||
@@ -44,19 +50,12 @@ const insertFiles = function (
|
||||
onShowToast,
|
||||
} = options;
|
||||
|
||||
invariant(
|
||||
uploadFile,
|
||||
"uploadFile callback must be defined to handle uploads."
|
||||
);
|
||||
|
||||
// okay, we have some dropped files and a handler – lets stop this
|
||||
// event going any further up the stack
|
||||
event.preventDefault();
|
||||
|
||||
// let the user know we're starting to process the files
|
||||
if (onFileUploadStart) {
|
||||
onFileUploadStart();
|
||||
}
|
||||
onFileUploadStart?.();
|
||||
|
||||
const { schema } = view.state;
|
||||
|
||||
@@ -66,7 +65,10 @@ const insertFiles = function (
|
||||
|
||||
const filesToUpload = files.map((file) => ({
|
||||
id: `upload-${uuidv4()}`,
|
||||
isImage: file.type.startsWith("image/") && !options.isAttachment,
|
||||
isImage:
|
||||
FileHelper.isImage(file) && !options.isAttachment && !!schema.nodes.image,
|
||||
isVideo:
|
||||
FileHelper.isVideo(file) && !options.isAttachment && !!schema.nodes.video,
|
||||
file,
|
||||
}));
|
||||
|
||||
@@ -75,11 +77,6 @@ const insertFiles = function (
|
||||
const { tr } = view.state;
|
||||
|
||||
if (upload.isImage) {
|
||||
// Skip if the editor does not support images.
|
||||
if (!view.state.schema.nodes.image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// insert a placeholder at this position, or mark an existing file as being
|
||||
// replaced
|
||||
tr.setMeta(uploadPlaceholderPlugin, {
|
||||
@@ -92,6 +89,18 @@ const insertFiles = function (
|
||||
},
|
||||
});
|
||||
view.dispatch(tr);
|
||||
} else if (upload.isVideo) {
|
||||
// insert a placeholder at this position, or mark an existing file as being
|
||||
// replaced
|
||||
tr.setMeta(uploadPlaceholderPlugin, {
|
||||
add: {
|
||||
id: upload.id,
|
||||
file: upload.file,
|
||||
pos,
|
||||
isVideo: true,
|
||||
},
|
||||
});
|
||||
view.dispatch(tr);
|
||||
} else if (!attachmentPlaceholdersSet) {
|
||||
// Skip if the editor does not support attachments.
|
||||
if (!view.state.schema.nodes.attachment) {
|
||||
@@ -108,7 +117,7 @@ const insertFiles = function (
|
||||
attachmentsToUpload.map((attachment) =>
|
||||
schema.nodes.attachment.create({
|
||||
id: attachment.id,
|
||||
title: attachment.file.name ?? "Untitled",
|
||||
title: attachment.file.name ?? dictionary.untitled,
|
||||
size: attachment.file.size,
|
||||
})
|
||||
)
|
||||
@@ -120,8 +129,8 @@ const insertFiles = function (
|
||||
// start uploading the file to the server. Using "then" syntax
|
||||
// to allow all placeholders to be entered at once with the uploads
|
||||
// happening in the background in parallel.
|
||||
uploadFile(upload.file)
|
||||
.then((src) => {
|
||||
uploadFile?.(upload.file)
|
||||
.then(async (src) => {
|
||||
if (upload.isImage) {
|
||||
const newImg = new Image();
|
||||
newImg.onload = () => {
|
||||
@@ -161,6 +170,44 @@ const insertFiles = function (
|
||||
};
|
||||
|
||||
newImg.src = src;
|
||||
} else if (upload.isVideo) {
|
||||
const result = findPlaceholder(view.state, upload.id);
|
||||
|
||||
// if the content around the placeholder has been deleted
|
||||
// then forget about inserting this file
|
||||
if (result === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [from, to] = result;
|
||||
const dimensions = await FileHelper.getVideoDimensions(upload.file);
|
||||
|
||||
view.dispatch(
|
||||
view.state.tr
|
||||
.replaceWith(
|
||||
from,
|
||||
to || from,
|
||||
schema.nodes.video.create({
|
||||
src,
|
||||
title: upload.file.name ?? dictionary.untitled,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
...options.attrs,
|
||||
})
|
||||
)
|
||||
.setMeta(uploadPlaceholderPlugin, { remove: { id: upload.id } })
|
||||
);
|
||||
|
||||
// If the users selection is still at the file then make sure to select
|
||||
// the entire node once done. Otherwise, if the selection has moved
|
||||
// elsewhere then we don't want to modify it
|
||||
if (view.state.selection.from === from) {
|
||||
view.dispatch(
|
||||
view.state.tr.setSelection(
|
||||
new NodeSelection(view.state.doc.resolve(from))
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const result = findAttachmentById(view.state, upload.id);
|
||||
|
||||
@@ -176,7 +223,7 @@ const insertFiles = function (
|
||||
to || from,
|
||||
schema.nodes.attachment.create({
|
||||
href: src,
|
||||
title: upload.file.name ?? "Untitled",
|
||||
title: upload.file.name ?? dictionary.untitled,
|
||||
size: upload.file.size,
|
||||
})
|
||||
)
|
||||
@@ -198,7 +245,7 @@ const insertFiles = function (
|
||||
Sentry.captureException(error);
|
||||
|
||||
// cleanup the placeholder if there is a failure
|
||||
if (upload.isImage) {
|
||||
if (upload.isImage || upload.isVideo) {
|
||||
view.dispatch(
|
||||
view.state.tr.setMeta(uploadPlaceholderPlugin, {
|
||||
remove: { id: upload.id },
|
||||
|
||||
Reference in New Issue
Block a user