mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 21:11:09 +10:00
feat: enhance editor uploads (#895)
* * multi-file paste support * allow media files (image/videos) to be attachments * insert trailing node if file placeholder is at the end of the editor * fix video align
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||
import {
|
||||
insertTrailingNode,
|
||||
MediaUploadOptions,
|
||||
UploadFn,
|
||||
} from "../media-utils";
|
||||
import { IAttachment } from "../types";
|
||||
|
||||
const uploadKey = new PluginKey("attachment-upload");
|
||||
@ -33,7 +37,8 @@ export const AttachmentUploadPlugin = ({
|
||||
|
||||
placeholder.appendChild(uploadingText);
|
||||
|
||||
const deco = Decoration.widget(pos + 1, placeholder, {
|
||||
const realPos = pos + 1;
|
||||
const deco = Decoration.widget(realPos, placeholder, {
|
||||
id,
|
||||
});
|
||||
set = set.add(tr.doc, [deco]);
|
||||
@ -64,8 +69,8 @@ function findPlaceholder(state: EditorState, id: {}) {
|
||||
|
||||
export const handleAttachmentUpload =
|
||||
({ validateFn, onUpload }: MediaUploadOptions): UploadFn =>
|
||||
async (file, view, pos, pageId) => {
|
||||
const validated = validateFn?.(file);
|
||||
async (file, view, pos, pageId, allowMedia) => {
|
||||
const validated = validateFn?.(file, allowMedia);
|
||||
// @ts-ignore
|
||||
if (!validated) return;
|
||||
// A fresh object to act as the ID for this upload
|
||||
@ -82,6 +87,8 @@ export const handleAttachmentUpload =
|
||||
fileName: file.name,
|
||||
},
|
||||
});
|
||||
|
||||
insertTrailingNode(tr, pos, view);
|
||||
view.dispatch(tr);
|
||||
|
||||
await onUpload(file, pageId).then(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||
import { insertTrailingNode, MediaUploadOptions, UploadFn } from "../media-utils";
|
||||
import { IAttachment } from "../types";
|
||||
|
||||
const uploadKey = new PluginKey("image-upload");
|
||||
@ -69,13 +69,13 @@ export const handleImageUpload =
|
||||
// A fresh object to act as the ID for this upload
|
||||
const id = {};
|
||||
|
||||
// Replace the selection with a placeholder
|
||||
const tr = view.state.tr;
|
||||
if (!tr.selection.empty) tr.deleteSelection();
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const tr = view.state.tr;
|
||||
// Replace the selection with a placeholder
|
||||
if (!tr.selection.empty) tr.deleteSelection();
|
||||
|
||||
tr.setMeta(uploadKey, {
|
||||
add: {
|
||||
id,
|
||||
@ -83,6 +83,8 @@ export const handleImageUpload =
|
||||
src: reader.result,
|
||||
},
|
||||
});
|
||||
|
||||
insertTrailingNode(tr, pos, view);
|
||||
view.dispatch(tr);
|
||||
};
|
||||
|
||||
|
||||
@ -1,13 +1,29 @@
|
||||
import type { EditorView } from "@tiptap/pm/view";
|
||||
import { Transaction } from "@tiptap/pm/state";
|
||||
|
||||
export type UploadFn = (
|
||||
file: File,
|
||||
view: EditorView,
|
||||
pos: number,
|
||||
pageId: string,
|
||||
// only applicable to file attachments
|
||||
allowMedia?: boolean,
|
||||
) => void;
|
||||
|
||||
export interface MediaUploadOptions {
|
||||
validateFn?: (file: File) => void;
|
||||
validateFn?: (file: File, allowMedia?: boolean) => void;
|
||||
onUpload: (file: File, pageId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export function insertTrailingNode(
|
||||
tr: Transaction,
|
||||
pos: number,
|
||||
view: EditorView,
|
||||
) {
|
||||
// create trailing node after decoration
|
||||
// if decoration is at the last node
|
||||
const currentDocSize = view.state.doc.content.size;
|
||||
if (pos + 1 === currentDocSize) {
|
||||
tr.insert(currentDocSize, view.state.schema.nodes.paragraph.create());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||
import {
|
||||
insertTrailingNode,
|
||||
MediaUploadOptions,
|
||||
UploadFn,
|
||||
} from "../media-utils";
|
||||
import { IAttachment } from "../types";
|
||||
|
||||
const uploadKey = new PluginKey("video-upload");
|
||||
@ -70,12 +74,13 @@ export const handleVideoUpload =
|
||||
const id = {};
|
||||
|
||||
// Replace the selection with a placeholder
|
||||
const tr = view.state.tr;
|
||||
if (!tr.selection.empty) tr.deleteSelection();
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const tr = view.state.tr;
|
||||
if (!tr.selection.empty) tr.deleteSelection();
|
||||
|
||||
tr.setMeta(uploadKey, {
|
||||
add: {
|
||||
id,
|
||||
@ -83,6 +88,8 @@ export const handleVideoUpload =
|
||||
src: reader.result,
|
||||
},
|
||||
});
|
||||
|
||||
insertTrailingNode(tr, pos, view);
|
||||
view.dispatch(tr);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user