From f729e46bb0ebfce15bff4e061bada938718e87ad Mon Sep 17 00:00:00 2001 From: CoolCu <166844316+CoolCu@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:18:11 +0800 Subject: [PATCH 1/5] chore: fix some typos in comments (#4205) Signed-off-by: CoolCu --- apiserver/plane/api/views/cycle.py | 2 +- space/components/accounts/sign-in-forms/o-auth-options.tsx | 4 ++-- web/components/account/o-auth/o-auth-options.tsx | 4 ++-- web/components/core/render-if-visible-HOC.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index c7df49b39..d9c75ff41 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -746,7 +746,7 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView): class TransferCycleIssueAPIEndpoint(BaseAPIView): """ - This viewset provides `create` actions for transfering the issues into a particular cycle. + This viewset provides `create` actions for transferring the issues into a particular cycle. """ diff --git a/space/components/accounts/sign-in-forms/o-auth-options.tsx b/space/components/accounts/sign-in-forms/o-auth-options.tsx index b546d9d3f..d6222bccb 100644 --- a/space/components/accounts/sign-in-forms/o-auth-options.tsx +++ b/space/components/accounts/sign-in-forms/o-auth-options.tsx @@ -35,7 +35,7 @@ export const OAuthOptions: React.FC = observer((props) => { const response = await authService.socialAuth(socialAuthPayload); if (response) handleSignInRedirection(); - } else throw Error("Cant find credentials"); + } else throw Error("Can't find credentials"); } catch (err: any) { setToastAlert({ title: "Error signing in!", @@ -56,7 +56,7 @@ export const OAuthOptions: React.FC = observer((props) => { const response = await authService.socialAuth(socialAuthPayload); if (response) handleSignInRedirection(); - } else throw Error("Cant find credentials"); + } else throw Error("Can't find credentials"); } catch (err: any) { setToastAlert({ title: "Error signing in!", diff --git a/web/components/account/o-auth/o-auth-options.tsx b/web/components/account/o-auth/o-auth-options.tsx index 1dd970fd6..4e2c5e288 100644 --- a/web/components/account/o-auth/o-auth-options.tsx +++ b/web/components/account/o-auth/o-auth-options.tsx @@ -36,7 +36,7 @@ export const OAuthOptions: React.FC = observer((props) => { const response = await authService.socialAuth(socialAuthPayload); if (response) handleSignInRedirection(); - } else throw Error("Cant find credentials"); + } else throw Error("Can't find credentials"); } catch (err: any) { setToast({ type: TOAST_TYPE.ERROR, @@ -57,7 +57,7 @@ export const OAuthOptions: React.FC = observer((props) => { const response = await authService.socialAuth(socialAuthPayload); if (response) handleSignInRedirection(); - } else throw Error("Cant find credentials"); + } else throw Error("Can't find credentials"); } catch (err: any) { setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/components/core/render-if-visible-HOC.tsx b/web/components/core/render-if-visible-HOC.tsx index 585ba7769..b6d587559 100644 --- a/web/components/core/render-if-visible-HOC.tsx +++ b/web/components/core/render-if-visible-HOC.tsx @@ -24,7 +24,7 @@ const RenderIfVisible: React.FC = (props) => { as = "div", children, classNames = "", - alwaysRender = false, //render the children even if it is not visble in root + alwaysRender = false, //render the children even if it is not visible in root placeholderChildren = null, //placeholder children pauseHeightUpdateWhileRendering = false, //while this is true the height of the blocks are maintained changingReference, //This is to force render when this reference is changed From 2311bd4f78d7cfce3beab6354e8e5559f06b8c24 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:48:32 +0530 Subject: [PATCH 2/5] add a small min height for dropping issues incase tger are zero issues in the entire row of columns (#4206) --- web/components/issues/issue-layouts/kanban/kanban-group.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx index e913e0e31..d1161be2e 100644 --- a/web/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -166,7 +166,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
Date: Tue, 16 Apr 2024 15:50:06 +0530 Subject: [PATCH 3/5] fix empty blocks due to virtualization in rare cases (#4207) --- web/components/core/render-if-visible-HOC.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/core/render-if-visible-HOC.tsx b/web/components/core/render-if-visible-HOC.tsx index b6d587559..dcb9b5cf0 100644 --- a/web/components/core/render-if-visible-HOC.tsx +++ b/web/components/core/render-if-visible-HOC.tsx @@ -48,7 +48,7 @@ const RenderIfVisible: React.FC = (props) => { // } else { // setShouldVisible(entries[0].isIntersecting); // } - setShouldVisible(entries[0].isIntersecting); + setShouldVisible(entries[entries.length - 1].isIntersecting); }, { root: root?.current, From 480aa906de86db5937ef66b0c7f6b18c1cdbf55f Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:51:05 +0530 Subject: [PATCH 4/5] [WEB - 480] fix: horizontal rule drag and drop (#4200) * fix: horizontal rule parsing (with backwards compatibility) and ensuring correct selections * fix: table drag handle position * fix: code block drag handle positioning fixes * fix: blockquote duplication, selection and deletion fixed * fix: possible range errors and type errors --- packages/editor/core/src/styles/editor.css | 2 +- .../horizontal-rule/horizontal-rule.ts | 15 +++++- .../src/ui/menu/block-menu.tsx | 49 ++++++++++++++----- .../extensions/src/extensions/drag-drop.tsx | 41 ++++++++++++++-- 4 files changed, 86 insertions(+), 21 deletions(-) diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index 4cb4e9772..5868fce91 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -236,7 +236,7 @@ div[data-type="horizontalRule"] { margin-bottom: 0; & > div { - border-bottom: 1px solid rgb(var(--color-border-200)); + border-bottom: 2px solid rgb(var(--color-border-200)); } } diff --git a/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts b/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts index 2af845b7a..b9be1a314 100644 --- a/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts +++ b/packages/editor/core/src/ui/extensions/horizontal-rule/horizontal-rule.ts @@ -28,11 +28,22 @@ export const CustomHorizontalRule = Node.create({ group: "block", parseHTML() { - return [{ tag: "hr" }]; + return [ + { + tag: `div[data-type="${this.name}"]`, + }, + { tag: "hr" }, + ]; }, renderHTML({ HTMLAttributes }) { - return ["hr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]; + return [ + "div", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + "data-type": this.name, + }), + ["div", {}], + ]; }, addCommands() { diff --git a/packages/editor/document-editor/src/ui/menu/block-menu.tsx b/packages/editor/document-editor/src/ui/menu/block-menu.tsx index 9703113f7..6fc9a87fe 100644 --- a/packages/editor/document-editor/src/ui/menu/block-menu.tsx +++ b/packages/editor/document-editor/src/ui/menu/block-menu.tsx @@ -101,21 +101,44 @@ export default function BlockMenu(props: BlockMenuProps) { label: "Duplicate", isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image", onClick: (e) => { - const { view } = editor; - const { state } = view; - const { selection } = state; - - editor - .chain() - .insertContentAt(selection.to, selection.content().content.firstChild!.toJSON(), { - updateSelection: true, - }) - .focus(selection.to + 1, { scrollIntoView: false }) - .run(); - - popup.current?.hide(); e.preventDefault(); e.stopPropagation(); + + try { + const { state } = editor; + const { selection } = state; + const firstChild = selection.content().content.firstChild; + const docSize = state.doc.content.size; + + if (!firstChild) { + throw new Error("No content selected or content is not duplicable."); + } + + // Directly use selection.to as the insertion position + const insertPos = selection.to; + + // Ensure the insertion position is within the document's bounds + if (insertPos < 0 || insertPos > docSize) { + throw new Error("The insertion position is invalid or outside the document."); + } + + const contentToInsert = firstChild.toJSON(); + + // Insert the content at the calculated position + editor + .chain() + .insertContentAt(insertPos, contentToInsert, { + updateSelection: true, + }) + .focus(Math.min(insertPos + 1, docSize), { scrollIntoView: false }) + .run(); + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + } + } + + popup.current?.hide(); }, }, ]; diff --git a/packages/editor/extensions/src/extensions/drag-drop.tsx b/packages/editor/extensions/src/extensions/drag-drop.tsx index 1c2427418..e9ef9c06e 100644 --- a/packages/editor/extensions/src/extensions/drag-drop.tsx +++ b/packages/editor/extensions/src/extensions/drag-drop.tsx @@ -58,10 +58,10 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) { [ "li", "p:not(:first-child)", - "pre", + ".code-block", "blockquote", "h1, h2, h3", - ".table-wrapper", + "table", "[data-type=horizontalRule]", ].join(", ") ) @@ -77,10 +77,25 @@ function nodePosAtDOM(node: Element, view: EditorView, options: DragHandleOption })?.inside; } +function nodePosAtDOMForBlockquotes(node: Element, view: EditorView) { + const boundingRect = node.getBoundingClientRect(); + + return view.posAtCoords({ + left: boundingRect.left + 1, + top: boundingRect.top + 1, + })?.inside; +} + function calcNodePos(pos: number, view: EditorView) { - const $pos = view.state.doc.resolve(pos); - if ($pos.depth > 1) return $pos.before($pos.depth); - return pos; + const maxPos = view.state.doc.content.size; + const safePos = Math.max(0, Math.min(pos, maxPos)); + const $pos = view.state.doc.resolve(safePos); + + if ($pos.depth > 1) { + const newPos = $pos.before($pos.depth); + return Math.max(0, Math.min(newPos, maxPos)); + } + return safePos; } function DragHandle(options: DragHandleOptions) { @@ -156,6 +171,20 @@ function DragHandle(options: DragHandleOptions) { if (!(node instanceof Element)) return; + if (node.matches("blockquote")) { + let nodePosForBlockquotes = nodePosAtDOMForBlockquotes(node, view); + if (nodePosForBlockquotes === null || nodePosForBlockquotes === undefined) return; + + const docSize = view.state.doc.content.size; + nodePosForBlockquotes = Math.max(0, Math.min(nodePosForBlockquotes, docSize)); + + if (nodePosForBlockquotes >= 0 && nodePosForBlockquotes <= docSize) { + const nodeSelection = NodeSelection.create(view.state.doc, nodePosForBlockquotes); + view.dispatch(view.state.tr.setSelection(nodeSelection)); + } + return; + } + let nodePos = nodePosAtDOM(node, view, options); if (nodePos === null || nodePos === undefined) return; @@ -244,11 +273,13 @@ function DragHandle(options: DragHandleOptions) { rect.top += (lineHeight - 20) / 2; rect.top += paddingTop; + // Li markers if (node.matches("ul:not([data-type=taskList]) li, ol li")) { rect.top += 4; rect.left -= 18; } + rect.width = options.dragHandleWidth; if (!dragHandleElement) return; From c18265c7cf145d54f8247d058da722cf018cbfe8 Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:52:31 +0530 Subject: [PATCH 5/5] [WEB-480] fix: image uploading with swr sync fixed for pages (#4187) * fix: stroing the transactions in page * fix: page details changes * chore: page response change * chore: removed duplicated endpoints * chore: optimised the urls * chore: removed archived and favorite pages * chore: revamping pages store and components * mentions loading state part done * fixed mentions not showing in modals * removed comments and cleaned up types * removed unused types * reset: head * chore: pages store and component updates * style: pages list item UI * fix: improved colors and drag handle width * fix: slash commands are no more shown in the code blocks * fix: cleanup/hide drag handles post drop * fix: hide/cleanup drag handles post drag start * fix: aligning the drag handles better with the node post css changes of the length * fix: juggling back and forth of drag handles in ordered and unordered lists * chore: fix imports, ts errors and other things * fix: clearing nodes to default node i.e paragraph before converting it to other types of nodes For more reference on what this does, please refer https://tiptap.dev/docs/editor/api/commands/clear-nodes * chore: clearNodes after delete in case of selections being present * fix: hiding link selector in the bubble menu if inline code block is selected * chore: filtering, ordering and searching implemented * chore: updated pages store and updated UI * chore: new core editor just for document editor created * chore: removed setIsSubmitting prop in doc editor * fix: fixed submitting state for image uploads * refactor: setShouldShowAlert removed * refactor: rerenderOnPropsChange prop removed * chore: type inference magic in ref to expose an api for controlling editor menu items from outside * fix: naming imports * chore: change names of the exposed functions and removing old types * refactor: remove debouncedUpdatesEnabled prop; * refactor: editor heading markings now parsed using html * chore: removed unrelated components from the document editor * refactor: page details granular components * fix: remove onActionCompleteHandler * refactor: removed rerenderOnProps change prop * feat: added getMarkDown function * chore: update dropdown option actions * fix: sidebar markings update logic * chore: add image and to-do list actions to the toolbar * fix: handling refs and populating them via callbacks * feat: scroll to node api exposed * cleaning up editor refs when the editor is destroyed * feat: scrolling added to read only instance of the editor * fix: markings logic * fix: build errors with types * fix: build erros * fix: subscribing to transactions of editor via ref * chore: remove debug statements * fix: type errors * fix: temporary different slash commands for document editor * chore: inline code extension style * chore: remove border from readOnly editor * fix: editor bottom padding * chore: pages improvements * chore: handle Enter key on the page title * feat: added loading indicator logic in mentions * fix: mentions and slash commands now work well with multiple editors in one place * refactor: page store structure, filtering logic * feat: added better seperation in inline code blocks * feat: list autojoining added * fix: pages folder structure * fix: image refocus from external parts * working lists somewhat * chore: implement page reactions * fix: build errors * fix: build errors * fixed drag handles stuff * task list item fixed * working * fix: working on multiple nested lists * chore: remove debug statements * fix: Tab key on first list item handled to not go out of editor focus * feat: threshold auto scroll support added and multi nested list selection fixed * fix: caret color bug with improved inline code blocks * fix: node range error when bulk deleting with list * fix: removed slash commands from working in code blocks * chore: update typography margins * chore: new field added in page model * fix: better type inference in slash commands * chore: code block UI * feat: image insertion at correct position using ref added * feat: added improved mentions support for space * fix: type errors in mentions for comments in web app * sync: core with document-core * fix: build errors * fix: fallback for appendTo not being able to find active container instantly * fix: page store * fix: page description * fix: css quality issues * chore: code cleanup * chore: removed placeholder text in codeblocks * chore: archived pages response change * chore: archived pages response change * fix: initial pages list fetch * fix: pages list filters and ordering * chore: add access change option in the quick actions dropdown * fix: inline code block caret fixed * regression: removing extra text * chore: caret color removed * feat: copy code button added in code blocks * fix: initial load of page details * fix: initial load of page details * fix: image resizing weird behavior on click/expanding it too much fixed now * chore: copy page response * fix: todo list spacing * chore: description html in the copy page * chore: handle latest description on refetch * fix: saner scroll behaviours * fix: block menu positioning * fix: updated empty string description * feat: tab change sync support added * fix: infinite rerendering with markings * fix: block menu finally * fix: intial load on reload bug fixed * fix: nested lists alignment * fix: editor padding * fix: first level list items copyable * chore: list spacing * fix: title change * fix: pages list block items interaction * fix: saving chip position * fix: delete action from block menu to focus properly * fix: margin-bottom as 0 to avoid weird spacing when a paragraph node follows a list node * style: table, chore: lite text editor toolbar * fix: page description tab sync * fix: lists spacing and alignment * refactor: document editor props * feat: rich text editor wrapper created and migrated core * feat: created wrapper around lite text editor and merged core * chore: add lite text editor toolbar * fix: build errors * fix: type errors and addead live updation of toolbar * chore: pages migration * fix: inbox issue * refactor: remove redundant package * refactor: unused files * fix: add dompurify to space app * fix: inline code margin * fix: editor className props * fix: build errors * fix: traversing up the tree before assuming the parent is not a list item * fix: drag handle positions for list items fixed * fix: removed focus at end logic after deleting block * fix: image wrapper overflow scroll fix with block menu's position * fix: selection and deletion logic for nested lists fixed!! * fix: hiding the block menu while scrolling in the document/app * fix: merge conflicts resolved from develop * fix: inbox issue description * chore: move page title to the web app * fix: handling edge cases for table selection * chore: lint issues * refactor: list item functions moved to same file * refactor: use mention hook * fix: added try catch blocks for mention suggestions * chore: remove unused code * fix: remove console logs * fix: remove console logs * fix: tracking image uploading status to prevent sync rerenders * chore: remove unnecessary props * fix: type of cancel button changed to button instead of default submit * feat: editor focus on saved position while syncing via swr --------- Co-authored-by: NarayanBavisetti Co-authored-by: sriram veeraghanta Co-authored-by: gurusainath Co-authored-by: Aaryan Khandelwal --- packages/editor/core/src/hooks/use-editor.tsx | 37 ++++++++++---- .../editor/core/src/lib/editor-commands.ts | 2 +- .../editor/core/src/ui/extensions/drop.tsx | 45 +++++++++++++++++ .../core/src/ui/extensions/image/index.tsx | 5 +- .../editor/core/src/ui/extensions/index.tsx | 11 +++-- .../core/src/ui/plugins/upload-image.tsx | 48 +++++++++++++++---- packages/editor/core/src/ui/props.tsx | 41 ++-------------- 7 files changed, 125 insertions(+), 64 deletions(-) create mode 100644 packages/editor/core/src/ui/extensions/drop.tsx diff --git a/packages/editor/core/src/hooks/use-editor.tsx b/packages/editor/core/src/hooks/use-editor.tsx index e3ef76483..e432ec6a4 100644 --- a/packages/editor/core/src/hooks/use-editor.tsx +++ b/packages/editor/core/src/hooks/use-editor.tsx @@ -19,7 +19,7 @@ interface CustomEditorProps { uploadFile: UploadImage; restoreFile: RestoreImage; deleteFile: DeleteImage; - cancelUploadImage?: () => any; + cancelUploadImage?: () => void; initialValue: string; editorClassName: string; // undefined when prop is not passed, null if intentionally passed to stop @@ -56,7 +56,7 @@ export const useEditor = ({ }: CustomEditorProps) => { const editor = useCustomEditor({ editorProps: { - ...CoreEditorProps(uploadFile, editorClassName), + ...CoreEditorProps(editorClassName), ...editorProps, }, extensions: [ @@ -69,6 +69,7 @@ export const useEditor = ({ deleteFile, restoreFile, cancelUploadImage, + uploadFile, }, placeholder, }), @@ -89,19 +90,37 @@ export const useEditor = ({ }, }); - // for syncing swr data on tab refocus etc, can remove it once this is merged - // https://github.com/ueberdosis/tiptap/pull/4453 + const editorRef: MutableRefObject = useRef(null); + + const [savedSelection, setSavedSelection] = useState(null); + + // Inside your component or hook + const savedSelectionRef = useRef(savedSelection); + + // Update the ref whenever savedSelection changes + useEffect(() => { + savedSelectionRef.current = savedSelection; + }, [savedSelection]); + + // Effect for syncing SWR data useEffect(() => { // value is null when intentionally passed where syncing is not yet // supported and value is undefined when the data from swr is not populated if (value === null || value === undefined) return; - if (editor && !editor.isDestroyed) editor?.commands.setContent(value); + if (editor && !editor.isDestroyed && !editor.storage.image.uploadInProgress) { + editor.commands.setContent(value); + const currentSavedSelection = savedSelectionRef.current; + if (currentSavedSelection) { + editor.view.focus(); + const docLength = editor.state.doc.content.size; + const relativePosition = Math.min(currentSavedSelection.from, docLength - 1); + editor.commands.setTextSelection(relativePosition); + } else { + editor.commands.focus("end"); + } + } }, [editor, value, id]); - const editorRef: MutableRefObject = useRef(null); - - const [savedSelection, setSavedSelection] = useState(null); - useImperativeHandle( forwardedRef, () => ({ diff --git a/packages/editor/core/src/lib/editor-commands.ts b/packages/editor/core/src/lib/editor-commands.ts index e163b17dd..ce14502a7 100644 --- a/packages/editor/core/src/lib/editor-commands.ts +++ b/packages/editor/core/src/lib/editor-commands.ts @@ -126,7 +126,7 @@ export const insertImageCommand = ( if (input.files?.length) { const file = input.files[0]; const pos = savedSelection?.anchor ?? editor.view.state.selection.from; - startImageUpload(file, editor.view, pos, uploadFile); + startImageUpload(editor, file, editor.view, pos, uploadFile); } }; input.click(); diff --git a/packages/editor/core/src/ui/extensions/drop.tsx b/packages/editor/core/src/ui/extensions/drop.tsx new file mode 100644 index 000000000..8de48f9e0 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/drop.tsx @@ -0,0 +1,45 @@ +import { Extension } from "@tiptap/core"; +import { Plugin, PluginKey } from "prosemirror-state"; +import { UploadImage } from "src/types/upload-image"; +import { startImageUpload } from "../plugins/upload-image"; + +export const DropHandlerExtension = (uploadFile: UploadImage) => + Extension.create({ + name: "dropHandler", + priority: 1000, + + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey("dropHandler"), + props: { + handlePaste: (view, event) => { + if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) { + event.preventDefault(); + const file = event.clipboardData.files[0]; + const pos = view.state.selection.from; + startImageUpload(this.editor, file, view, pos, uploadFile); + return true; + } + return false; + }, + handleDrop: (view, event, _slice, moved) => { + if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + const coordinates = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + if (coordinates) { + startImageUpload(this.editor, file, view, coordinates.pos - 1, uploadFile); + } + return true; + } + return false; + }, + }, + }), + ]; + }, + }); diff --git a/packages/editor/core/src/ui/extensions/image/index.tsx b/packages/editor/core/src/ui/extensions/image/index.tsx index 1431b7755..b85100fe5 100644 --- a/packages/editor/core/src/ui/extensions/image/index.tsx +++ b/packages/editor/core/src/ui/extensions/image/index.tsx @@ -18,7 +18,7 @@ interface ImageNode extends ProseMirrorNode { const deleteKey = new PluginKey("delete-image"); const IMAGE_NODE_TYPE = "image"; -export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => any) => +export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => void) => ImageExt.extend({ addKeyboardShortcuts() { return { @@ -28,7 +28,7 @@ export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreIma }, addProseMirrorPlugins() { return [ - UploadImagesPlugin(cancelUploadImage), + UploadImagesPlugin(this.editor, cancelUploadImage), new Plugin({ key: deleteKey, appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { @@ -124,6 +124,7 @@ export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreIma addStorage() { return { images: new Map(), + uploadInProgress: false, }; }, diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx index 0a9dd34ed..c5a5d5eb9 100644 --- a/packages/editor/core/src/ui/extensions/index.tsx +++ b/packages/editor/core/src/ui/extensions/index.tsx @@ -29,6 +29,8 @@ import { CustomCodeInlineExtension } from "src/ui/extensions/code-inline"; import { CustomTypographyExtension } from "src/ui/extensions/typography"; import { CustomHorizontalRule } from "src/ui/extensions/horizontal-rule/horizontal-rule"; import { CustomCodeMarkPlugin } from "./custom-code-inline/inline-code-plugin"; +import { UploadImage } from "src/types/upload-image"; +import { DropHandlerExtension } from "./drop"; type TArguments = { mentionConfig: { @@ -38,14 +40,15 @@ type TArguments = { fileConfig: { deleteFile: DeleteImage; restoreFile: RestoreImage; - cancelUploadImage?: () => any; + cancelUploadImage?: () => void; + uploadFile: UploadImage; }; placeholder?: string | ((isFocused: boolean) => string); }; export const CoreEditorExtensions = ({ mentionConfig, - fileConfig: { deleteFile, restoreFile, cancelUploadImage }, + fileConfig: { deleteFile, restoreFile, cancelUploadImage, uploadFile }, placeholder, }: TArguments) => [ StarterKit.configure({ @@ -73,10 +76,8 @@ export const CoreEditorExtensions = ({ width: 1, }, }), - // BulletList, - // OrderedList, - // ListItem, CustomQuoteExtension, + DropHandlerExtension(uploadFile), CustomHorizontalRule.configure({ HTMLAttributes: { class: "my-4 border-custom-border-400", diff --git a/packages/editor/core/src/ui/plugins/upload-image.tsx b/packages/editor/core/src/ui/plugins/upload-image.tsx index 3f6a40bf9..af56d5382 100644 --- a/packages/editor/core/src/ui/plugins/upload-image.tsx +++ b/packages/editor/core/src/ui/plugins/upload-image.tsx @@ -1,12 +1,22 @@ +import { Editor } from "@tiptap/core"; import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; import { UploadImage } from "src/types/upload-image"; const uploadKey = new PluginKey("upload-image"); -export const UploadImagesPlugin = (cancelUploadImage?: () => any) => - new Plugin({ +export const UploadImagesPlugin = (editor: Editor, cancelUploadImage?: () => void) => { + let currentView: EditorView | null = null; + return new Plugin({ key: uploadKey, + view(editorView) { + currentView = editorView; + return { + destroy() { + currentView = null; + }, + }; + }, state: { init() { return DecorationSet.empty; @@ -27,13 +37,17 @@ export const UploadImagesPlugin = (cancelUploadImage?: () => any) => // Create cancel button const cancelButton = document.createElement("button"); + cancelButton.type = "button"; cancelButton.style.position = "absolute"; cancelButton.style.right = "3px"; cancelButton.style.top = "3px"; cancelButton.setAttribute("class", "opacity-90 rounded-lg"); cancelButton.onclick = () => { - cancelUploadImage?.(); + if (currentView) { + cancelUploadImage?.(); + removePlaceholder(editor, currentView, id); + } }; // Create an SVG element from the SVG string @@ -59,6 +73,7 @@ export const UploadImagesPlugin = (cancelUploadImage?: () => any) => }, }, }); +}; function findPlaceholder(state: EditorState, id: {}) { const decos = uploadKey.getState(state); @@ -66,26 +81,38 @@ function findPlaceholder(state: EditorState, id: {}) { return found.length ? found[0].from : null; } -const removePlaceholder = (view: EditorView, id: {}) => { +const removePlaceholder = (editor: Editor, view: EditorView, id: {}) => { const removePlaceholderTr = view.state.tr.setMeta(uploadKey, { remove: { id }, }); view.dispatch(removePlaceholderTr); + editor.storage.image.uploadInProgress = false; }; -export async function startImageUpload(file: File, view: EditorView, pos: number, uploadFile: UploadImage) { +export async function startImageUpload( + editor: Editor, + file: File, + view: EditorView, + pos: number, + uploadFile: UploadImage +) { + editor.storage.image.uploadInProgress = true; + if (!file) { alert("No file selected. Please select a file to upload."); + editor.storage.image.uploadInProgress = false; return; } if (!file.type.includes("image/")) { alert("Invalid file type. Please select an image file."); + editor.storage.image.uploadInProgress = false; return; } if (file.size > 5 * 1024 * 1024) { alert("File size too large. Please select a file smaller than 5MB."); + editor.storage.image.uploadInProgress = false; return; } @@ -110,7 +137,7 @@ export async function startImageUpload(file: File, view: EditorView, pos: number // Handle FileReader errors reader.onerror = (error) => { console.error("FileReader error: ", error); - removePlaceholder(view, id); + removePlaceholder(editor, view, id); return; }; @@ -121,7 +148,10 @@ export async function startImageUpload(file: File, view: EditorView, pos: number const { schema } = view.state; pos = findPlaceholder(view.state, id); - if (pos == null) return; + if (pos == null) { + editor.storage.image.uploadInProgress = false; + return; + } const imageSrc = typeof src === "object" ? reader.result : src; const node = schema.nodes.image.create({ src: imageSrc }); @@ -129,9 +159,9 @@ export async function startImageUpload(file: File, view: EditorView, pos: number view.dispatch(transaction); view.focus(); + editor.storage.image.uploadInProgress = false; } catch (error) { - console.error("Upload error: ", error); - removePlaceholder(view, id); + removePlaceholder(editor, view, id); } } diff --git a/packages/editor/core/src/ui/props.tsx b/packages/editor/core/src/ui/props.tsx index aa88fa042..3d46b5840 100644 --- a/packages/editor/core/src/ui/props.tsx +++ b/packages/editor/core/src/ui/props.tsx @@ -1,9 +1,7 @@ import { EditorProps } from "@tiptap/pm/view"; -import { cn, findTableAncestor } from "src/lib/utils"; -import { UploadImage } from "src/types/upload-image"; -import { startImageUpload } from "src/ui/plugins/upload-image"; +import { cn } from "src/lib/utils"; -export function CoreEditorProps(uploadFile: UploadImage, editorClassName: string): EditorProps { +export function CoreEditorProps(editorClassName: string): EditorProps { return { attributes: { class: cn( @@ -17,45 +15,12 @@ export function CoreEditorProps(uploadFile: UploadImage, editorClassName: string if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) { const slashCommand = document.querySelector("#slash-command"); if (slashCommand) { + console.log("registered"); return true; } } }, }, - handlePaste: (view, event) => { - if (typeof window !== "undefined") { - const selection: any = window?.getSelection(); - if (selection.rangeCount !== 0) { - const range = selection.getRangeAt(0); - if (findTableAncestor(range.startContainer)) { - return; - } - } - } - if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) { - event.preventDefault(); - const file = event.clipboardData.files[0]; - const pos = view.state.selection.from; - startImageUpload(file, view, pos, uploadFile); - return true; - } - return false; - }, - handleDrop: (view, event, _slice, moved) => { - if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { - event.preventDefault(); - const file = event.dataTransfer.files[0]; - const coordinates = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); - if (coordinates) { - startImageUpload(file, view, coordinates.pos - 1, uploadFile); - } - return true; - } - return false; - }, transformPastedHTML(html) { return html.replace(//g, ""); },