fix: Image restoration fixed (marks/unmarks an image to be deleted after a week) (#2859)
* image restoration fixed (marks an image to be deleted after a week) * removed clgs * added image constraints * formatted editor-core package using yarn format * lite-text-editor nothing to format * rich-text-editor nothing to format * formatted document-editor with prettier * modified file service to follow api change * fixed more formatting in document editor * fixed all instances of types with that from the package * fixed delete to work consistently (minor optimizations turned off) * stop duplicate images inside editor * restore image on editor creation say if user A deletes image number 2, user B was also in the same issue and in their screen the image was there, if user B makes certain changes and that gets saved in backend, according to user B image 2 should exist but since user A deleted it, it'll not get restored and get deleted in 7 days, hence I've added a check such that whenever a issue loads we restore all images by default * added restore image function with types * replaced all instances to have restore image logic * fixed issue detail for peek view * disabled option to insert table inside a table
This commit is contained in:
parent
0fcadca53a
commit
e01ca97fc9
63 changed files with 471 additions and 225 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { UploadImage } from "@plane/editor-types";
|
||||
import { Editor, Range } from "@tiptap/core";
|
||||
import { UploadImage } from "../types/upload-image";
|
||||
import { startImageUpload } from "../ui/plugins/upload-image";
|
||||
import { findTableAncestor } from "./utils";
|
||||
|
||||
export const toggleHeadingOne = (editor: Editor, range?: Range) => {
|
||||
if (range)
|
||||
|
|
@ -95,6 +96,15 @@ export const toggleBlockquote = (editor: Editor, range?: Range) => {
|
|||
};
|
||||
|
||||
export const insertTableCommand = (editor: Editor, range?: Range) => {
|
||||
if (typeof window !== "undefined") {
|
||||
const selection: any = window?.getSelection();
|
||||
if (selection.rangeCount !== 0) {
|
||||
const range = selection.getRangeAt(0);
|
||||
if (findTableAncestor(range.startContainer)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (range)
|
||||
editor
|
||||
.chain()
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export type IMentionSuggestion = {
|
||||
id: string;
|
||||
type: string;
|
||||
avatar: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
redirect_uri: string;
|
||||
};
|
||||
|
||||
export type IMentionHighlight = string;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export type UploadImage = (file: File) => Promise<string>;
|
||||
|
|
@ -1,30 +1,33 @@
|
|||
import { getNodeType } from '@tiptap/core'
|
||||
import { NodeType } from '@tiptap/pm/model'
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
import { getNodeType } from "@tiptap/core";
|
||||
import { NodeType } from "@tiptap/pm/model";
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
|
||||
const { $from } = state.selection
|
||||
const nodeType = getNodeType(typeOrName, state.schema)
|
||||
export const findListItemPos = (
|
||||
typeOrName: string | NodeType,
|
||||
state: EditorState,
|
||||
) => {
|
||||
const { $from } = state.selection;
|
||||
const nodeType = getNodeType(typeOrName, state.schema);
|
||||
|
||||
let currentNode = null
|
||||
let currentDepth = $from.depth
|
||||
let currentPos = $from.pos
|
||||
let targetDepth: number | null = null
|
||||
let currentNode = null;
|
||||
let currentDepth = $from.depth;
|
||||
let currentPos = $from.pos;
|
||||
let targetDepth: number | null = null;
|
||||
|
||||
while (currentDepth > 0 && targetDepth === null) {
|
||||
currentNode = $from.node(currentDepth)
|
||||
currentNode = $from.node(currentDepth);
|
||||
|
||||
if (currentNode.type === nodeType) {
|
||||
targetDepth = currentDepth
|
||||
targetDepth = currentDepth;
|
||||
} else {
|
||||
currentDepth -= 1
|
||||
currentPos -= 1
|
||||
currentDepth -= 1;
|
||||
currentPos -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDepth === null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return { $pos: state.doc.resolve(currentPos), depth: targetDepth }
|
||||
}
|
||||
return { $pos: state.doc.resolve(currentPos), depth: targetDepth };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import { EditorState } from '@tiptap/pm/state'
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
export const hasListBefore = (editorState: EditorState, name: string, parentListTypes: string[]) => {
|
||||
const { $anchor } = editorState.selection
|
||||
export const hasListBefore = (
|
||||
editorState: EditorState,
|
||||
name: string,
|
||||
parentListTypes: string[],
|
||||
) => {
|
||||
const { $anchor } = editorState.selection;
|
||||
|
||||
const previousNodePos = Math.max(0, $anchor.pos - 2)
|
||||
const previousNodePos = Math.max(0, $anchor.pos - 2);
|
||||
|
||||
const previousNode = editorState.doc.resolve(previousNodePos).node()
|
||||
const previousNode = editorState.doc.resolve(previousNodePos).node();
|
||||
|
||||
if (!previousNode || !parentListTypes.includes(previousNode.type.name)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import { EditorState } from '@tiptap/pm/state'
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
export const hasListItemAfter = (typeOrName: string, state: EditorState): boolean => {
|
||||
const { $anchor } = state.selection
|
||||
export const hasListItemAfter = (
|
||||
typeOrName: string,
|
||||
state: EditorState,
|
||||
): boolean => {
|
||||
const { $anchor } = state.selection;
|
||||
|
||||
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2)
|
||||
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2);
|
||||
|
||||
if ($targetPos.index() === $targetPos.parent.childCount - 1) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($targetPos.nodeAfter?.type.name !== typeOrName) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import { EditorState } from '@tiptap/pm/state'
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
export const hasListItemBefore = (typeOrName: string, state: EditorState): boolean => {
|
||||
const { $anchor } = state.selection
|
||||
export const hasListItemBefore = (
|
||||
typeOrName: string,
|
||||
state: EditorState,
|
||||
): boolean => {
|
||||
const { $anchor } = state.selection;
|
||||
|
||||
const $targetPos = state.doc.resolve($anchor.pos - 2)
|
||||
const $targetPos = state.doc.resolve($anchor.pos - 2);
|
||||
|
||||
if ($targetPos.index() === 0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($targetPos.nodeBefore?.type.name !== typeOrName) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,135 @@
|
|||
import Image from "@tiptap/extension-image";
|
||||
import TrackImageDeletionPlugin from "../../plugins/delete-image";
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import UploadImagesPlugin from "../../plugins/upload-image";
|
||||
import { DeleteImage } from "../../../types/delete-image";
|
||||
import ImageExt from "@tiptap/extension-image";
|
||||
import { onNodeDeleted, onNodeRestored } from "../../plugins/delete-image";
|
||||
import { DeleteImage, RestoreImage } from "@plane/editor-types";
|
||||
|
||||
interface ImageNode extends ProseMirrorNode {
|
||||
attrs: {
|
||||
src: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const deleteKey = new PluginKey("delete-image");
|
||||
const IMAGE_NODE_TYPE = "image";
|
||||
|
||||
const ImageExtension = (
|
||||
deleteImage: DeleteImage,
|
||||
restoreFile: RestoreImage,
|
||||
cancelUploadImage?: () => any,
|
||||
) =>
|
||||
Image.extend({
|
||||
ImageExt.extend({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
UploadImagesPlugin(cancelUploadImage),
|
||||
TrackImageDeletionPlugin(deleteImage),
|
||||
new Plugin({
|
||||
key: deleteKey,
|
||||
appendTransaction: (
|
||||
transactions: readonly Transaction[],
|
||||
oldState: EditorState,
|
||||
newState: EditorState,
|
||||
) => {
|
||||
const newImageSources = new Set<string>();
|
||||
newState.doc.descendants((node) => {
|
||||
if (node.type.name === IMAGE_NODE_TYPE) {
|
||||
newImageSources.add(node.attrs.src);
|
||||
}
|
||||
});
|
||||
|
||||
transactions.forEach((transaction) => {
|
||||
// transaction could be a selection
|
||||
if (!transaction.docChanged) return;
|
||||
|
||||
const removedImages: ImageNode[] = [];
|
||||
|
||||
// iterate through all the nodes in the old state
|
||||
oldState.doc.descendants((oldNode, oldPos) => {
|
||||
// if the node is not an image, then return as no point in checking
|
||||
if (oldNode.type.name !== IMAGE_NODE_TYPE) return;
|
||||
|
||||
// Check if the node has been deleted or replaced
|
||||
if (!newImageSources.has(oldNode.attrs.src)) {
|
||||
removedImages.push(oldNode as ImageNode);
|
||||
}
|
||||
});
|
||||
|
||||
removedImages.forEach(async (node) => {
|
||||
const src = node.attrs.src;
|
||||
this.storage.images.set(src, true);
|
||||
await onNodeDeleted(src, deleteImage);
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
new Plugin({
|
||||
key: new PluginKey("imageRestoration"),
|
||||
appendTransaction: (
|
||||
transactions: readonly Transaction[],
|
||||
oldState: EditorState,
|
||||
newState: EditorState,
|
||||
) => {
|
||||
const oldImageSources = new Set<string>();
|
||||
oldState.doc.descendants((node) => {
|
||||
if (node.type.name === IMAGE_NODE_TYPE) {
|
||||
oldImageSources.add(node.attrs.src);
|
||||
}
|
||||
});
|
||||
|
||||
transactions.forEach((transaction) => {
|
||||
if (!transaction.docChanged) return;
|
||||
|
||||
const addedImages: ImageNode[] = [];
|
||||
|
||||
newState.doc.descendants((node, pos) => {
|
||||
if (node.type.name !== IMAGE_NODE_TYPE) return;
|
||||
if (pos < 0 || pos > newState.doc.content.size) return;
|
||||
if (oldImageSources.has(node.attrs.src)) return;
|
||||
addedImages.push(node as ImageNode);
|
||||
});
|
||||
|
||||
addedImages.forEach(async (image) => {
|
||||
const wasDeleted = this.storage.images.get(image.attrs.src);
|
||||
if (wasDeleted === undefined) {
|
||||
this.storage.images.set(image.attrs.src, false);
|
||||
} else if (wasDeleted === true) {
|
||||
await onNodeRestored(image.attrs.src, restoreFile);
|
||||
}
|
||||
});
|
||||
});
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
||||
onCreate(this) {
|
||||
const imageSources = new Set<string>();
|
||||
this.editor.state.doc.descendants((node) => {
|
||||
if (node.type.name === IMAGE_NODE_TYPE) {
|
||||
imageSources.add(node.attrs.src);
|
||||
}
|
||||
});
|
||||
imageSources.forEach(async (src) => {
|
||||
try {
|
||||
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
|
||||
await restoreFile(assetUrlWithWorkspaceId);
|
||||
} catch (error) {
|
||||
console.error("Error restoring image: ", error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// storage to keep track of image states Map<src, isDeleted>
|
||||
addStorage() {
|
||||
return {
|
||||
images: new Map<string, boolean>(),
|
||||
};
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
|
|
|
|||
|
|
@ -15,14 +15,17 @@ import HorizontalRule from "./horizontal-rule";
|
|||
|
||||
import ImageExtension from "./image";
|
||||
|
||||
import { DeleteImage } from "../../types/delete-image";
|
||||
import { isValidHttpUrl } from "../../lib/utils";
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
import { Mentions } from "../mentions";
|
||||
|
||||
import { CustomKeymap } from "./keymap";
|
||||
import { CustomCodeBlock } from "./code";
|
||||
import { ListKeymap } from "./custom-list-keymap";
|
||||
import {
|
||||
IMentionSuggestion,
|
||||
DeleteImage,
|
||||
RestoreImage,
|
||||
} from "@plane/editor-types";
|
||||
|
||||
export const CoreEditorExtensions = (
|
||||
mentionConfig: {
|
||||
|
|
@ -30,6 +33,7 @@ export const CoreEditorExtensions = (
|
|||
mentionHighlights: string[];
|
||||
},
|
||||
deleteFile: DeleteImage,
|
||||
restoreFile: RestoreImage,
|
||||
cancelUploadImage?: () => any,
|
||||
) => [
|
||||
StarterKit.configure({
|
||||
|
|
@ -71,7 +75,7 @@ export const CoreEditorExtensions = (
|
|||
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
|
||||
},
|
||||
}),
|
||||
ImageExtension(deleteFile, cancelUploadImage).configure({
|
||||
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({
|
||||
HTMLAttributes: {
|
||||
class: "rounded-lg border border-custom-border-300",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
|
||||
import { useImperativeHandle, useRef, MutableRefObject } from "react";
|
||||
import { DeleteImage } from "../../types/delete-image";
|
||||
import { CoreEditorProps } from "../props";
|
||||
import { CoreEditorExtensions } from "../extensions";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { getTrimmedHTML } from "../../lib/utils";
|
||||
import { UploadImage } from "../../types/upload-image";
|
||||
import { useInitializedContent } from "./useInitializedContent";
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
import {
|
||||
DeleteImage,
|
||||
IMentionSuggestion,
|
||||
RestoreImage,
|
||||
UploadImage,
|
||||
} from "@plane/editor-types";
|
||||
|
||||
interface CustomEditorProps {
|
||||
uploadFile: UploadImage;
|
||||
restoreFile: RestoreImage;
|
||||
deleteFile: DeleteImage;
|
||||
cancelUploadImage?: () => any;
|
||||
setIsSubmitting?: (
|
||||
isSubmitting: "submitting" | "submitted" | "saved",
|
||||
) => void;
|
||||
setShouldShowAlert?: (showAlert: boolean) => void;
|
||||
value: string;
|
||||
deleteFile: DeleteImage;
|
||||
debouncedUpdatesEnabled?: boolean;
|
||||
onStart?: (json: any, html: string) => void;
|
||||
onChange?: (json: any, html: string) => void;
|
||||
|
|
@ -25,7 +30,6 @@ interface CustomEditorProps {
|
|||
forwardedRef?: any;
|
||||
mentionHighlights?: string[];
|
||||
mentionSuggestions?: IMentionSuggestion[];
|
||||
cancelUploadImage?: () => any;
|
||||
}
|
||||
|
||||
export const useEditor = ({
|
||||
|
|
@ -39,6 +43,7 @@ export const useEditor = ({
|
|||
onChange,
|
||||
setIsSubmitting,
|
||||
forwardedRef,
|
||||
restoreFile,
|
||||
setShouldShowAlert,
|
||||
mentionHighlights,
|
||||
mentionSuggestions,
|
||||
|
|
@ -56,6 +61,7 @@ export const useEditor = ({
|
|||
mentionHighlights: mentionHighlights ?? [],
|
||||
},
|
||||
deleteFile,
|
||||
restoreFile,
|
||||
cancelUploadImage,
|
||||
),
|
||||
...extensions,
|
||||
|
|
@ -63,7 +69,7 @@ export const useEditor = ({
|
|||
content:
|
||||
typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
|
||||
onCreate: async ({ editor }) => {
|
||||
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()))
|
||||
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
||||
},
|
||||
onUpdate: async ({ editor }) => {
|
||||
// for instant feedback loop
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions";
|
||||
import { CoreReadOnlyEditorProps } from "../../ui/read-only/props";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
|
||||
interface CustomReadOnlyEditorProps {
|
||||
value: string;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
"use client";
|
||||
import * as React from "react";
|
||||
import { Extension } from "@tiptap/react";
|
||||
import { UploadImage } from "../types/upload-image";
|
||||
import { DeleteImage } from "../types/delete-image";
|
||||
import { getEditorClassNames } from "../lib/utils";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { useEditor } from "./hooks/useEditor";
|
||||
import { EditorContainer } from "../ui/components/editor-container";
|
||||
import { EditorContentWrapper } from "../ui/components/editor-content";
|
||||
import { IMentionSuggestion } from "../types/mention-suggestion";
|
||||
import {
|
||||
UploadImage,
|
||||
DeleteImage,
|
||||
IMentionSuggestion,
|
||||
} from "@plane/editor-types";
|
||||
|
||||
interface ICoreEditor {
|
||||
value: string;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import React, {
|
||||
forwardRef,
|
||||
|
|
@ -7,8 +8,6 @@ import React, {
|
|||
useState,
|
||||
} from "react";
|
||||
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
|
||||
interface MentionListProps {
|
||||
items: IMentionSuggestion[];
|
||||
command: (item: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { Mention, MentionOptions } from "@tiptap/extension-mention";
|
|||
import { mergeAttributes } from "@tiptap/core";
|
||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||
import mentionNodeView from "./mentionNodeView";
|
||||
import { IMentionHighlight } from "../../types/mention-suggestion";
|
||||
import { IMentionHighlight } from "@plane/editor-types";
|
||||
|
||||
export interface CustomMentionOptions extends MentionOptions {
|
||||
mentionHighlights: IMentionHighlight[];
|
||||
readonly?: boolean;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
import suggestion from "./suggestion";
|
||||
import { CustomMention } from "./custom";
|
||||
import {
|
||||
IMentionHighlight,
|
||||
IMentionSuggestion,
|
||||
} from "../../types/mention-suggestion";
|
||||
import { IMentionHighlight, IMentionSuggestion } from "@plane/editor-types";
|
||||
|
||||
export const Mentions = (
|
||||
mentionSuggestions: IMentionSuggestion[],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { NodeViewWrapper } from "@tiptap/react";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { useRouter } from "next/router";
|
||||
import { IMentionHighlight } from "../../types/mention-suggestion";
|
||||
import { IMentionHighlight } from "@plane/editor-types";
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default (props) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Editor } from "@tiptap/core";
|
|||
import tippy from "tippy.js";
|
||||
|
||||
import MentionList from "./MentionList";
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
|
||||
const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
||||
items: ({ query }: { query: string }) =>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
CodeIcon,
|
||||
} from "lucide-react";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { UploadImage } from "../../../types/upload-image";
|
||||
import {
|
||||
insertImageCommand,
|
||||
insertTableCommand,
|
||||
|
|
@ -32,6 +31,7 @@ import {
|
|||
toggleTaskList,
|
||||
toggleUnderline,
|
||||
} from "../../../lib/editor-commands";
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
|
||||
export interface EditorMenuItem {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import { DeleteImage } from "../../types/delete-image";
|
||||
import { DeleteImage, RestoreImage } from "@plane/editor-types";
|
||||
|
||||
const deleteKey = new PluginKey("delete-image");
|
||||
const IMAGE_NODE_TYPE = "image";
|
||||
|
|
@ -59,7 +59,7 @@ const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
|
|||
|
||||
export default TrackImageDeletionPlugin;
|
||||
|
||||
async function onNodeDeleted(
|
||||
export async function onNodeDeleted(
|
||||
src: string,
|
||||
deleteImage: DeleteImage,
|
||||
): Promise<void> {
|
||||
|
|
@ -73,3 +73,18 @@ async function onNodeDeleted(
|
|||
console.error("Error deleting image: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function onNodeRestored(
|
||||
src: string,
|
||||
restoreImage: RestoreImage,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
|
||||
const resStatus = await restoreImage(assetUrlWithWorkspaceId);
|
||||
if (resStatus === 204) {
|
||||
console.log("Image restored successfully");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error restoring image: ", error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { UploadImage } from "../../types/upload-image";
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { UploadImage } from "@plane/editor-types";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { findTableAncestor } from "../lib/utils";
|
||||
import { startImageUpload } from "./plugins/upload-image";
|
||||
import { UploadImage } from "../types/upload-image";
|
||||
|
||||
export function CoreEditorProps(
|
||||
uploadFile: UploadImage,
|
||||
|
|
@ -82,5 +82,8 @@ export function CoreEditorProps(
|
|||
}
|
||||
return false;
|
||||
},
|
||||
transformPastedHTML(html) {
|
||||
return html.replace(/<img.*?>/g, "");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import TableRow from "../extensions/table/table-row/table-row";
|
|||
import ReadOnlyImageExtension from "../extensions/image/read-only-image";
|
||||
import { isValidHttpUrl } from "../../lib/utils";
|
||||
import { Mentions } from "../mentions";
|
||||
import { IMentionSuggestion } from "../../types/mention-suggestion";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
|
||||
export const CoreReadOnlyEditorExtensions = (mentionConfig: {
|
||||
mentionSuggestions: IMentionSuggestion[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue