* stash * refactor, working in non-collab + collab editor * attachment styling * Avoid crypto require in browser * AttachmentIcon, handling unknown types * Do not allow attachment creation for file sizes over limit * Allow image as file attachment * Upload placeholder styling * lint * Refactor: Do not use placeholder for file attachmentuploads * Add loading spinner * fix: Extra paragraphs around attachments on insert * Bump editor * fix build error * Remove attachment placeholder when upload fails * Remove unused styles * fix: Attachments on shared pages * Merge fixes
102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { EditorState, Plugin } from "prosemirror-state";
|
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
import * as React from "react";
|
|
import ReactDOM from "react-dom";
|
|
import FileExtension from "../components/FileExtension";
|
|
|
|
// based on the example at: https://prosemirror.net/examples/upload/
|
|
const uploadPlaceholder = new Plugin({
|
|
state: {
|
|
init() {
|
|
return DecorationSet.empty;
|
|
},
|
|
apply(tr, set: DecorationSet) {
|
|
// Adjust decoration positions to changes made by the transaction
|
|
set = set.map(tr.mapping, tr.doc);
|
|
|
|
// See if the transaction adds or removes any placeholders
|
|
const action = tr.getMeta(this);
|
|
|
|
if (action?.add) {
|
|
if (action.add.replaceExisting) {
|
|
const $pos = tr.doc.resolve(action.add.pos);
|
|
|
|
if ($pos.nodeAfter?.type.name === "image") {
|
|
const deco = Decoration.node(
|
|
$pos.pos,
|
|
$pos.pos + $pos.nodeAfter.nodeSize,
|
|
{
|
|
class: "image-replacement-uploading",
|
|
},
|
|
{
|
|
id: action.add.id,
|
|
}
|
|
);
|
|
set = set.add(tr.doc, [deco]);
|
|
}
|
|
} else if (action.add.isImage) {
|
|
const element = document.createElement("div");
|
|
element.className = "image placeholder";
|
|
|
|
const img = document.createElement("img");
|
|
img.src = URL.createObjectURL(action.add.file);
|
|
|
|
element.appendChild(img);
|
|
|
|
const deco = Decoration.widget(action.add.pos, element, {
|
|
id: action.add.id,
|
|
});
|
|
set = set.add(tr.doc, [deco]);
|
|
} else {
|
|
const element = document.createElement("div");
|
|
element.className = "attachment placeholder";
|
|
|
|
const icon = document.createElement("div");
|
|
icon.className = "icon";
|
|
|
|
const component = <FileExtension title={action.add.file.name} />;
|
|
ReactDOM.render(component, icon);
|
|
element.appendChild(icon);
|
|
|
|
const text = document.createElement("span");
|
|
text.innerText = action.add.file.name;
|
|
element.appendChild(text);
|
|
|
|
const status = document.createElement("span");
|
|
status.innerText = "Uploading…";
|
|
status.className = "status";
|
|
element.appendChild(status);
|
|
|
|
const deco = Decoration.widget(action.add.pos, element, {
|
|
id: action.add.id,
|
|
});
|
|
set = set.add(tr.doc, [deco]);
|
|
}
|
|
}
|
|
|
|
if (action?.remove) {
|
|
set = set.remove(
|
|
set.find(undefined, undefined, (spec) => spec.id === action.remove.id)
|
|
);
|
|
}
|
|
return set;
|
|
},
|
|
},
|
|
props: {
|
|
decorations(state) {
|
|
return this.getState(state);
|
|
},
|
|
},
|
|
});
|
|
|
|
export default uploadPlaceholder;
|
|
|
|
export function findPlaceholder(
|
|
state: EditorState,
|
|
id: string
|
|
): [number, number] | null {
|
|
const decos: DecorationSet = uploadPlaceholder.getState(state);
|
|
const found = decos.find(undefined, undefined, (spec) => spec.id === id);
|
|
return found.length ? [found[0].from, found[0].to] : null;
|
|
}
|