From 56331000eb66acc9964a140df9e3a438f2a1a97c Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:07:28 +0530 Subject: [PATCH 1/7] cancellable uploads and image limits with better error handling --- .../core/src/ui/extensions/image/index.tsx | 39 +++-- .../editor/core/src/ui/extensions/index.tsx | 141 +++++++++--------- .../editor/core/src/ui/hooks/useEditor.tsx | 19 ++- .../core/src/ui/plugins/delete-image.tsx | 11 +- .../core/src/ui/plugins/upload-image.tsx | 108 +++++++++++--- .../editor/lite-text-editor/src/ui/index.tsx | 3 + .../editor/rich-text-editor/src/ui/index.tsx | 51 ++++--- web/components/issues/description-form.tsx | 8 +- web/services/file.service.ts | 16 +- 9 files changed, 264 insertions(+), 132 deletions(-) diff --git a/packages/editor/core/src/ui/extensions/image/index.tsx b/packages/editor/core/src/ui/extensions/image/index.tsx index f9345509d98..aea84c6b884 100644 --- a/packages/editor/core/src/ui/extensions/image/index.tsx +++ b/packages/editor/core/src/ui/extensions/image/index.tsx @@ -3,21 +3,28 @@ import TrackImageDeletionPlugin from "../../plugins/delete-image"; import UploadImagesPlugin from "../../plugins/upload-image"; import { DeleteImage } from "../../../types/delete-image"; -const ImageExtension = (deleteImage: DeleteImage) => Image.extend({ - addProseMirrorPlugins() { - return [UploadImagesPlugin(), TrackImageDeletionPlugin(deleteImage)]; - }, - addAttributes() { - return { - ...this.parent?.(), - width: { - default: "35%", - }, - height: { - default: null, - }, - }; - }, -}); +const ImageExtension = ( + deleteImage: DeleteImage, + cancelUploadImage?: () => any, +) => + Image.extend({ + addProseMirrorPlugins() { + return [ + UploadImagesPlugin(cancelUploadImage), + TrackImageDeletionPlugin(deleteImage), + ]; + }, + addAttributes() { + return { + ...this.parent?.(), + width: { + default: "35%", + }, + height: { + default: null, + }, + }; + }, + }); export default ImageExtension; diff --git a/packages/editor/core/src/ui/extensions/index.tsx b/packages/editor/core/src/ui/extensions/index.tsx index a7621ab20cb..3f191a9127c 100644 --- a/packages/editor/core/src/ui/extensions/index.tsx +++ b/packages/editor/core/src/ui/extensions/index.tsx @@ -20,82 +20,89 @@ import { isValidHttpUrl } from "../../lib/utils"; import { IMentionSuggestion } from "../../types/mention-suggestion"; import { Mentions } from "../mentions"; - export const CoreEditorExtensions = ( - mentionConfig: { mentionSuggestions: IMentionSuggestion[], mentionHighlights: string[] }, + mentionConfig: { + mentionSuggestions: IMentionSuggestion[]; + mentionHighlights: string[]; + }, deleteFile: DeleteImage, + cancelUploadImage?: () => any, ) => [ - StarterKit.configure({ - bulletList: { - HTMLAttributes: { - class: "list-disc list-outside leading-3 -mt-2", - }, - }, - orderedList: { - HTMLAttributes: { - class: "list-decimal list-outside leading-3 -mt-2", - }, - }, - listItem: { - HTMLAttributes: { - class: "leading-normal -mb-2", - }, - }, - blockquote: { - HTMLAttributes: { - class: "border-l-4 border-custom-border-300", - }, - }, - code: { - HTMLAttributes: { - class: - "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", - spellcheck: "false", - }, - }, - codeBlock: false, - horizontalRule: false, - dropcursor: { - color: "rgba(var(--color-text-100))", - width: 2, + StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: "list-disc list-outside leading-3 -mt-2", }, - gapcursor: false, - }), - Gapcursor, - TiptapLink.configure({ - protocols: ["http", "https"], - validate: (url) => isValidHttpUrl(url), + }, + orderedList: { HTMLAttributes: { - class: - "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", + class: "list-decimal list-outside leading-3 -mt-2", }, - }), - ImageExtension(deleteFile).configure({ + }, + listItem: { HTMLAttributes: { - class: "rounded-lg border border-custom-border-300", + class: "leading-normal -mb-2", }, - }), - TiptapUnderline, - TextStyle, - Color, - TaskList.configure({ + }, + blockquote: { HTMLAttributes: { - class: "not-prose pl-2", + class: "border-l-4 border-custom-border-300", }, - }), - TaskItem.configure({ + }, + code: { HTMLAttributes: { - class: "flex items-start my-4", + class: + "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", + spellcheck: "false", }, - nested: true, - }), - Markdown.configure({ - html: true, - transformCopiedText: true, - }), - Table, - TableHeader, - TableCell, - TableRow, - Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false), - ]; + }, + codeBlock: false, + horizontalRule: false, + dropcursor: { + color: "rgba(var(--color-text-100))", + width: 2, + }, + gapcursor: false, + }), + Gapcursor, + TiptapLink.configure({ + protocols: ["http", "https"], + validate: (url) => isValidHttpUrl(url), + HTMLAttributes: { + class: + "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", + }, + }), + ImageExtension(deleteFile, cancelUploadImage).configure({ + HTMLAttributes: { + class: "rounded-lg border border-custom-border-300", + }, + }), + TiptapUnderline, + TextStyle, + Color, + TaskList.configure({ + HTMLAttributes: { + class: "not-prose pl-2", + }, + }), + TaskItem.configure({ + HTMLAttributes: { + class: "flex items-start my-4", + }, + nested: true, + }), + Markdown.configure({ + html: true, + transformCopiedText: true, + }), + Table, + TableHeader, + TableCell, + TableRow, + Mentions( + mentionConfig.mentionSuggestions, + mentionConfig.mentionHighlights, + false, + ), +]; diff --git a/packages/editor/core/src/ui/hooks/useEditor.tsx b/packages/editor/core/src/ui/hooks/useEditor.tsx index 9fcf200fb98..a0d55ce2777 100644 --- a/packages/editor/core/src/ui/hooks/useEditor.tsx +++ b/packages/editor/core/src/ui/hooks/useEditor.tsx @@ -29,11 +29,13 @@ interface CustomEditorProps { forwardedRef?: any; mentionHighlights?: string[]; mentionSuggestions?: IMentionSuggestion[]; + cancelUploadImage?: () => any; } export const useEditor = ({ uploadFile, deleteFile, + cancelUploadImage, editorProps = {}, value, extensions = [], @@ -42,7 +44,7 @@ export const useEditor = ({ forwardedRef, setShouldShowAlert, mentionHighlights, - mentionSuggestions + mentionSuggestions, }: CustomEditorProps) => { const editor = useCustomEditor( { @@ -50,7 +52,17 @@ export const useEditor = ({ ...CoreEditorProps(uploadFile, setIsSubmitting), ...editorProps, }, - extensions: [...CoreEditorExtensions({ mentionSuggestions: mentionSuggestions ?? [], mentionHighlights: mentionHighlights ?? []}, deleteFile), ...extensions], + extensions: [ + ...CoreEditorExtensions( + { + mentionSuggestions: mentionSuggestions ?? [], + mentionHighlights: mentionHighlights ?? [], + }, + deleteFile, + cancelUploadImage, + ), + ...extensions, + ], content: typeof value === "string" && value.trim() !== "" ? value : "

", onUpdate: async ({ editor }) => { @@ -82,4 +94,5 @@ export const useEditor = ({ } return editor; -}; \ No newline at end of file +}; + diff --git a/packages/editor/core/src/ui/plugins/delete-image.tsx b/packages/editor/core/src/ui/plugins/delete-image.tsx index 56284472b21..48ec244fcdc 100644 --- a/packages/editor/core/src/ui/plugins/delete-image.tsx +++ b/packages/editor/core/src/ui/plugins/delete-image.tsx @@ -15,7 +15,11 @@ interface ImageNode extends ProseMirrorNode { const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin => new Plugin({ key: deleteKey, - appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => { + appendTransaction: ( + transactions: readonly Transaction[], + oldState: EditorState, + newState: EditorState, + ) => { const newImageSources = new Set(); newState.doc.descendants((node) => { if (node.type.name === IMAGE_NODE_TYPE) { @@ -55,7 +59,10 @@ const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin => export default TrackImageDeletionPlugin; -async function onNodeDeleted(src: string, deleteImage: DeleteImage): Promise { +async function onNodeDeleted( + src: string, + deleteImage: DeleteImage, +): Promise { try { const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1); const resStatus = await deleteImage(assetUrlWithWorkspaceId); diff --git a/packages/editor/core/src/ui/plugins/upload-image.tsx b/packages/editor/core/src/ui/plugins/upload-image.tsx index cdd62ae4836..25646007313 100644 --- a/packages/editor/core/src/ui/plugins/upload-image.tsx +++ b/packages/editor/core/src/ui/plugins/upload-image.tsx @@ -4,7 +4,7 @@ import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view"; const uploadKey = new PluginKey("upload-image"); -const UploadImagesPlugin = () => +const UploadImagesPlugin = (cancelUploadImage?: () => any) => new Plugin({ key: uploadKey, state: { @@ -21,15 +21,46 @@ const UploadImagesPlugin = () => const placeholder = document.createElement("div"); placeholder.setAttribute("class", "img-placeholder"); const image = document.createElement("img"); - image.setAttribute("class", "opacity-10 rounded-lg border border-custom-border-300"); + image.setAttribute( + "class", + "opacity-10 rounded-lg border border-custom-border-300", + ); image.src = src; placeholder.appendChild(image); + + // Create cancel button + const cancelButton = document.createElement("button"); + cancelButton.style.position = "absolute"; + cancelButton.style.right = "3px"; + cancelButton.style.top = "3px"; + cancelButton.setAttribute("class", "opacity-90 rounded-lg"); + + cancelButton.onclick = () => { + cancelUploadImage?.(); + }; + + // Create an SVG element from the SVG string + const svgString = ``; + const parser = new DOMParser(); + const svgElement = parser.parseFromString( + svgString, + "image/svg+xml", + ).documentElement; + + cancelButton.appendChild(svgElement); + placeholder.appendChild(cancelButton); const deco = Decoration.widget(pos + 1, placeholder, { id, }); set = set.add(tr.doc, [deco]); } else if (action && action.remove) { - set = set.remove(set.find(undefined, undefined, (spec) => spec.id == action.remove.id)); + set = set.remove( + set.find( + undefined, + undefined, + (spec) => spec.id == action.remove.id, + ), + ); } return set; }, @@ -48,19 +79,39 @@ function findPlaceholder(state: EditorState, id: {}) { const found = decos.find( undefined, undefined, - (spec: { id: number | undefined }) => spec.id == id + (spec: { id: number | undefined }) => spec.id == id, ); return found.length ? found[0].from : null; } +const removePlaceholder = (view: EditorView, id: {}) => { + const removePlaceholderTr = view.state.tr.setMeta(uploadKey, { + remove: { id }, + }); + view.dispatch(removePlaceholderTr); +}; + export async function startImageUpload( file: File, view: EditorView, pos: number, uploadFile: UploadImage, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void, ) { + if (!file) { + alert("No file selected. Please select a file to upload."); + return; + } + if (!file.type.includes("image/")) { + alert("Invalid file type. Please select an image file."); + return; + } + + if (file.size > 5 * 1024 * 1024) { + alert("File size too large. Please select a file smaller than 5MB."); return; } @@ -82,28 +133,42 @@ export async function startImageUpload( view.dispatch(tr); }; + // Handle FileReader errors + reader.onerror = (error) => { + console.error("FileReader error: ", error); + removePlaceholder(view, id); + return; + }; + setIsSubmitting?.("submitting"); - const src = await UploadImageHandler(file, uploadFile); - const { schema } = view.state; - pos = findPlaceholder(view.state, id); - - if (pos == null) return; - const imageSrc = typeof src === "object" ? reader.result : src; - - const node = schema.nodes.image.create({ src: imageSrc }); - const transaction = view.state.tr - .replaceWith(pos, pos, node) - .setMeta(uploadKey, { remove: { id } }); - view.dispatch(transaction); + + try { + const src = await UploadImageHandler(file, uploadFile); + const { schema } = view.state; + pos = findPlaceholder(view.state, id); + + if (pos == null) return; + const imageSrc = typeof src === "object" ? reader.result : src; + + const node = schema.nodes.image.create({ src: imageSrc }); + const transaction = view.state.tr + .replaceWith(pos, pos, node) + .setMeta(uploadKey, { remove: { id } }); + view.dispatch(transaction); + } catch (error) { + console.error("Upload error: ", error); + removePlaceholder(view, id); + } } -const UploadImageHandler = (file: File, - uploadFile: UploadImage +const UploadImageHandler = ( + file: File, + uploadFile: UploadImage, ): Promise => { try { return new Promise(async (resolve, reject) => { try { - const imageUrl = await uploadFile(file) + const imageUrl = await uploadFile(file); const image = new Image(); image.src = imageUrl; @@ -118,9 +183,6 @@ const UploadImageHandler = (file: File, } }); } catch (error) { - if (error instanceof Error) { - console.log(error.message); - } return Promise.reject(error); } }; diff --git a/packages/editor/lite-text-editor/src/ui/index.tsx b/packages/editor/lite-text-editor/src/ui/index.tsx index 6cd03bcfa3e..d8d90f75f4a 100644 --- a/packages/editor/lite-text-editor/src/ui/index.tsx +++ b/packages/editor/lite-text-editor/src/ui/index.tsx @@ -48,6 +48,7 @@ interface ILiteTextEditor { }[]; }; onEnterKeyPress?: (e?: any) => void; + cancelUploadImage?: () => any; mentionHighlights?: string[]; mentionSuggestions?: IMentionSuggestion[]; submitButton?: React.ReactNode; @@ -65,6 +66,7 @@ interface EditorHandle { const LiteTextEditor = (props: LiteTextEditorProps) => { const { onChange, + cancelUploadImage, debouncedUpdatesEnabled, setIsSubmitting, setShouldShowAlert, @@ -85,6 +87,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => { const editor = useEditor({ onChange, + cancelUploadImage, debouncedUpdatesEnabled, setIsSubmitting, setShouldShowAlert, diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx index a0dbe7226e6..2e98a72aae3 100644 --- a/packages/editor/rich-text-editor/src/ui/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/index.tsx @@ -1,8 +1,13 @@ -"use client" -import * as React from 'react'; -import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from '@plane/editor-core'; -import { EditorBubbleMenu } from './menus/bubble-menu'; -import { RichTextEditorExtensions } from './extensions'; +"use client"; +import * as React from "react"; +import { + EditorContainer, + EditorContentWrapper, + getEditorClassNames, + useEditor, +} from "@plane/editor-core"; +import { EditorBubbleMenu } from "./menus/bubble-menu"; +import { RichTextEditorExtensions } from "./extensions"; export type UploadImage = (file: File) => Promise; export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise; @@ -14,9 +19,9 @@ export type IMentionSuggestion = { title: string; subtitle: string; redirect_uri: string; -} +}; -export type IMentionHighlight = string +export type IMentionHighlight = string; interface IRichTextEditor { value: string; @@ -24,10 +29,13 @@ interface IRichTextEditor { deleteFile: DeleteImage; noBorder?: boolean; borderOnFocus?: boolean; + cancelUploadImage?: () => any; customClassName?: string; editorContentCustomClassNames?: string; onChange?: (json: any, html: string) => void; - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void; + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void; setShouldShowAlert?: (showAlert: boolean) => void; forwardedRef?: any; debouncedUpdatesEnabled?: boolean; @@ -54,11 +62,12 @@ const RichTextEditor = ({ uploadFile, deleteFile, noBorder, + cancelUploadImage, borderOnFocus, customClassName, forwardedRef, mentionHighlights, - mentionSuggestions + mentionSuggestions, }: RichTextEditorProps) => { const editor = useEditor({ onChange, @@ -67,14 +76,19 @@ const RichTextEditor = ({ setShouldShowAlert, value, uploadFile, + cancelUploadImage, deleteFile, forwardedRef, extensions: RichTextEditorExtensions(uploadFile, setIsSubmitting), mentionHighlights, - mentionSuggestions + mentionSuggestions, }); - const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + const editorClassNames = getEditorClassNames({ + noBorder, + borderOnFocus, + customClassName, + }); if (!editor) return null; @@ -82,16 +96,19 @@ const RichTextEditor = ({ {editor && }
- +
-
+ ); }; -const RichTextEditorWithRef = React.forwardRef((props, ref) => ( - -)); +const RichTextEditorWithRef = React.forwardRef( + (props, ref) => , +); RichTextEditorWithRef.displayName = "RichTextEditorWithRef"; -export { RichTextEditor, RichTextEditorWithRef}; +export { RichTextEditor, RichTextEditorWithRef }; diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 6dd569f8fd0..b0ec17fb1be 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -38,7 +38,7 @@ export const IssueDescriptionForm: FC = (props) => { const { setShowAlert } = useReloadConfirmations(); - const editorSuggestion = useEditorSuggestions(workspaceSlug, issue.project_id) + const editorSuggestion = useEditorSuggestions(workspaceSlug, issue.project_id); const { handleSubmit, @@ -145,6 +145,7 @@ export const IssueDescriptionForm: FC = (props) => { control={control} render={({ field: { value, onChange } }) => ( = (props) => { )} />
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
diff --git a/web/services/file.service.ts b/web/services/file.service.ts index 0e3749a4cd8..84907161e3b 100644 --- a/web/services/file.service.ts +++ b/web/services/file.service.ts @@ -2,6 +2,7 @@ import { APIService } from "services/api.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; +import axios from "axios"; export interface UnSplashImage { id: string; @@ -28,25 +29,38 @@ export interface UnSplashImageUrls { } export class FileService extends APIService { + private cancelSource: any; + constructor() { super(API_BASE_URL); this.uploadFile = this.uploadFile.bind(this); this.deleteImage = this.deleteImage.bind(this); + this.cancelUpload = this.cancelUpload.bind(this); } async uploadFile(workspaceSlug: string, file: FormData): Promise { + this.cancelSource = axios.CancelToken.source(); return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, { headers: { ...this.getHeaders(), "Content-Type": "multipart/form-data", }, + cancelToken: this.cancelSource.token, }) .then((response) => response?.data) .catch((error) => { - throw error?.response?.data; + if (axios.isCancel(error)) { + console.log(error.message); + } else { + throw error?.response?.data; + } }); } + cancelUpload() { + this.cancelSource.cancel("Upload cancelled"); + } + getUploadFileFunction(workspaceSlug: string): (file: File) => Promise { return async (file: File) => { const formData = new FormData(); From 52c10ecf1b060a84bed9758a9f53e1012402015a Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:51:11 +0530 Subject: [PATCH 2/7] fixed table row/column picker behaviour on modals --- .../ui/extensions/table/table/table-view.tsx | 12 +++++++--- space/styles/table.css | 24 +++++++++++++++---- web/services/file.service.ts | 1 + web/styles/table.css | 18 +++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/editor/core/src/ui/extensions/table/table/table-view.tsx b/packages/editor/core/src/ui/extensions/table/table/table-view.tsx index 6e3f9318e98..7f72a212eb0 100644 --- a/packages/editor/core/src/ui/extensions/table/table/table-view.tsx +++ b/packages/editor/core/src/ui/extensions/table/table/table-view.tsx @@ -202,6 +202,7 @@ function createToolbox({ "div", { className: "toolboxItem", + itemType: "button", onClick() { onClickItem(item); }, @@ -253,6 +254,7 @@ function createColorPickerToolbox({ "div", { className: "toolboxItem", + itemType: "button", onClick: () => { onSelectColor(value); colorPicker.hide(); @@ -331,7 +333,9 @@ export class TableView implements NodeView { this.rowsControl = h( "div", { className: "rowsControl" }, - h("button", { + h("div", { + itemType: "button", + className: "rowsControlDiv", onClick: () => this.selectRow(), }), ); @@ -339,7 +343,9 @@ export class TableView implements NodeView { this.columnsControl = h( "div", { className: "columnsControl" }, - h("button", { + h("div", { + itemType: "button", + className: "columnsControlDiv", onClick: () => this.selectColumn(), }), ); @@ -352,7 +358,7 @@ export class TableView implements NodeView { ); this.columnsToolbox = createToolbox({ - triggerButton: this.columnsControl.querySelector("button"), + triggerButton: this.columnsControl.querySelector(".columnsControlDiv"), items: columnsToolboxItems, tippyOptions: { ...defaultTippyOptions, diff --git a/space/styles/table.css b/space/styles/table.css index ad88fd10ec8..8a47a8c59fd 100644 --- a/space/styles/table.css +++ b/space/styles/table.css @@ -92,7 +92,7 @@ transform: translateY(-50%); } -.tableWrapper .tableControls .columnsControl > button { +.tableWrapper .tableControls .columnsControl .columnsControlDiv { color: white; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); width: 30px; @@ -104,26 +104,42 @@ transform: translateX(-50%); } -.tableWrapper .tableControls .rowsControl > button { +.tableWrapper .tableControls .rowsControl .rowsControlDiv { color: white; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M12 3c-.825 0-1.5.675-1.5 1.5S11.175 6 12 6s1.5-.675 1.5-1.5S12.825 3 12 3zm0 15c-.825 0-1.5.675-1.5 1.5S11.175 21 12 21s1.5-.675 1.5-1.5S12.825 18 12 18zm0-7.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); height: 30px; width: 15px; } -.tableWrapper .tableControls button { +.tableWrapper .tableControls .rowsControlDiv { background-color: rgba(var(--color-primary-100)); border: 1px solid rgba(var(--color-border-200)); border-radius: 2px; background-size: 1.25rem; background-repeat: no-repeat; background-position: center; - transition: transform ease-out 100ms, background-color ease-out 100ms; + transition: + transform ease-out 100ms, + background-color ease-out 100ms; outline: none; box-shadow: #000 0px 2px 4px; cursor: pointer; } +.tableWrapper .tableControls .columnsControlDiv { + background-color: rgba(var(--color-primary-100)); + border: 1px solid rgba(var(--color-border-200)); + border-radius: 2px; + background-size: 1.25rem; + background-repeat: no-repeat; + background-position: center; + transition: + transform ease-out 100ms, + background-color ease-out 100ms; + outline: none; + box-shadow: #000 0px 2px 4px; + cursor: pointer; +} .tableWrapper .tableControls .tableToolbox, .tableWrapper .tableControls .tableColorPickerToolbox { border: 1px solid rgba(var(--color-border-300)); diff --git a/web/services/file.service.ts b/web/services/file.service.ts index 84907161e3b..46d747b60c0 100644 --- a/web/services/file.service.ts +++ b/web/services/file.service.ts @@ -49,6 +49,7 @@ export class FileService extends APIService { }) .then((response) => response?.data) .catch((error) => { + console.log(workspaceSlug,"delted" ,error); if (axios.isCancel(error)) { console.log(error.message); } else { diff --git a/web/styles/table.css b/web/styles/table.css index ad88fd10ec8..bce7e4683ad 100644 --- a/web/styles/table.css +++ b/web/styles/table.css @@ -92,7 +92,7 @@ transform: translateY(-50%); } -.tableWrapper .tableControls .columnsControl > button { +.tableWrapper .tableControls .columnsControl .columnsControlDiv { color: white; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); width: 30px; @@ -104,14 +104,14 @@ transform: translateX(-50%); } -.tableWrapper .tableControls .rowsControl > button { +.tableWrapper .tableControls .rowsControl .rowsControlDiv { color: white; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath fill='%238F95B2' d='M12 3c-.825 0-1.5.675-1.5 1.5S11.175 6 12 6s1.5-.675 1.5-1.5S12.825 3 12 3zm0 15c-.825 0-1.5.675-1.5 1.5S11.175 21 12 21s1.5-.675 1.5-1.5S12.825 18 12 18zm0-7.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z'/%3E%3C/svg%3E"); height: 30px; width: 15px; } -.tableWrapper .tableControls button { +.tableWrapper .tableControls .rowsControlDiv { background-color: rgba(var(--color-primary-100)); border: 1px solid rgba(var(--color-border-200)); border-radius: 2px; @@ -124,6 +124,18 @@ cursor: pointer; } +.tableWrapper .tableControls .columnsControlDiv { + background-color: rgba(var(--color-primary-100)); + border: 1px solid rgba(var(--color-border-200)); + border-radius: 2px; + background-size: 1.25rem; + background-repeat: no-repeat; + background-position: center; + transition: transform ease-out 100ms, background-color ease-out 100ms; + outline: none; + box-shadow: #000 0px 2px 4px; + cursor: pointer; +} .tableWrapper .tableControls .tableToolbox, .tableWrapper .tableControls .tableColorPickerToolbox { border: 1px solid rgba(var(--color-border-300)); From f98e3a1a0dbbb100d62f601ac3a62ce22030ee80 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:37:50 +0530 Subject: [PATCH 3/7] Merge branch 'rerender-debounce-editor-fix' into editor-draggable-nodes --- .../issues/peek-overview/comment/add-comment.tsx | 1 + .../peek-overview/comment/comment-detail-card.tsx | 1 + space/services/file.service.ts | 15 ++++++++++++++- .../inbox/modals/create-issue-modal.tsx | 1 + web/components/issues/comment/add-comment.tsx | 1 + web/components/issues/comment/comment-card.tsx | 1 + web/components/issues/draft-issue-form.tsx | 1 + web/components/issues/form.tsx | 1 + .../issue-peek-overview/activity/comment-card.tsx | 1 + .../activity/comment-editor.tsx | 1 + .../issues/issue-peek-overview/issue-detail.tsx | 1 + .../pages/create-update-block-inline.tsx | 2 ++ web/components/pages/single-page-block.tsx | 1 + web/services/file.service.ts | 1 - 14 files changed, 27 insertions(+), 2 deletions(-) diff --git a/space/components/issues/peek-overview/comment/add-comment.tsx b/space/components/issues/peek-overview/comment/add-comment.tsx index f70a2c5aad2..9878fd00a17 100644 --- a/space/components/issues/peek-overview/comment/add-comment.tsx +++ b/space/components/issues/peek-overview/comment/add-comment.tsx @@ -76,6 +76,7 @@ export const AddComment: React.FC = observer((props) => { handleSubmit(onSubmit)(e); }); }} + cancelUploadImage={fileService.cancelUpload} uploadFile={fileService.getUploadFileFunction(workspace_slug as string)} deleteFile={fileService.deleteImage} ref={editorRef} diff --git a/space/components/issues/peek-overview/comment/comment-detail-card.tsx b/space/components/issues/peek-overview/comment/comment-detail-card.tsx index 29801c9e648..ab09b2490ee 100644 --- a/space/components/issues/peek-overview/comment/comment-detail-card.tsx +++ b/space/components/issues/peek-overview/comment/comment-detail-card.tsx @@ -103,6 +103,7 @@ export const CommentCard: React.FC = observer((props) => { render={({ field: { onChange, value } }) => ( { + this.cancelSource = axios.CancelToken.source(); return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, { headers: { ...this.getHeaders(), "Content-Type": "multipart/form-data", }, + cancelToken: this.cancelSource.token, }) .then((response) => response?.data) .catch((error) => { - throw error?.response?.data; + if (axios.isCancel(error)) { + console.log(error.message); + } else { + throw error?.response?.data; + } }); } + cancelUpload() { + this.cancelSource.cancel("Upload cancelled"); + } getUploadFileFunction(workspaceSlug: string): (file: File) => Promise { return async (file: File) => { const formData = new FormData(); diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index a468580f4e5..0cc53a3e749 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -134,6 +134,7 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { control={control} render={({ field: { value, onChange } }) => ( = ({ disabled = false, onSubmit, showAc render={({ field: { onChange: onCommentChange, value: commentValue } }) => ( = ({
= (props) => { control={control} render={({ field: { value, onChange } }) => ( = observer((props) => { control={control} render={({ field: { value, onChange } }) => ( = (props) => {
= (props) => { render={({ field: { onChange: onCommentChange, value: commentValue } }) => ( = (props) =
{errors.name ? errors.name.message : null} = ({ if (!data) return ( = ({ return ( = ({ block, projectDetails, showBl {showBlockDetails ? block.description_html.length > 7 && ( response?.data) .catch((error) => { - console.log(workspaceSlug,"delted" ,error); if (axios.isCancel(error)) { console.log(error.message); } else { From 5832754785f0031c41a2a40e76f9a91049b12c31 Mon Sep 17 00:00:00 2001 From: Henit Chobisa Date: Tue, 7 Nov 2023 20:53:17 +0530 Subject: [PATCH 4/7] fix: added mention suggestions and highlights in `create-issue-modal` --- web/components/inbox/modals/create-issue-modal.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index 0cc53a3e749..9a1d150994e 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -15,6 +15,7 @@ import { IssuePrioritySelect } from "components/issues/select"; import { Button, Input, ToggleSwitch } from "@plane/ui"; // types import { IIssue } from "types"; +import useEditorSuggestions from "hooks/use-editor-suggestions"; type Props = { isOpen: boolean; @@ -40,6 +41,8 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { const editorRef = useRef(null); + const editorSuggestion = useEditorSuggestions() + const router = useRouter(); const { workspaceSlug, projectId, inboxId } = router.query; @@ -144,6 +147,8 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { onChange={(description, description_html: string) => { onChange(description_html); }} + mentionSuggestions={editorSuggestion.mentionSuggestions} + mentionHighlights={editorSuggestion.mentionHighlights} /> )} /> From a188c0b0d5284fe80cf3b8630b189e0887fc6191 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:01:53 +0530 Subject: [PATCH 5/7] removed uncessary files --- .../table-menu/InsertBottomTableIcon.tsx | 16 ---- .../menus/table-menu/InsertLeftTableIcon.tsx | 15 ---- .../menus/table-menu/InsertRightTableIcon.tsx | 16 ---- .../menus/table-menu/InsertTopTableIcon.tsx | 15 ---- .../core/src/ui/menus/table-menu/tooltip.tsx | 77 ------------------- 5 files changed, 139 deletions(-) delete mode 100644 packages/editor/core/src/ui/menus/table-menu/InsertBottomTableIcon.tsx delete mode 100644 packages/editor/core/src/ui/menus/table-menu/InsertLeftTableIcon.tsx delete mode 100644 packages/editor/core/src/ui/menus/table-menu/InsertRightTableIcon.tsx delete mode 100644 packages/editor/core/src/ui/menus/table-menu/InsertTopTableIcon.tsx delete mode 100644 packages/editor/core/src/ui/menus/table-menu/tooltip.tsx diff --git a/packages/editor/core/src/ui/menus/table-menu/InsertBottomTableIcon.tsx b/packages/editor/core/src/ui/menus/table-menu/InsertBottomTableIcon.tsx deleted file mode 100644 index 0e42ba64824..00000000000 --- a/packages/editor/core/src/ui/menus/table-menu/InsertBottomTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertBottomTableIcon = (props: any) => ( - - - -); - -export default InsertBottomTableIcon; diff --git a/packages/editor/core/src/ui/menus/table-menu/InsertLeftTableIcon.tsx b/packages/editor/core/src/ui/menus/table-menu/InsertLeftTableIcon.tsx deleted file mode 100644 index 1fd75fe8754..00000000000 --- a/packages/editor/core/src/ui/menus/table-menu/InsertLeftTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertLeftTableIcon = (props: any) => ( - - - -); -export default InsertLeftTableIcon; diff --git a/packages/editor/core/src/ui/menus/table-menu/InsertRightTableIcon.tsx b/packages/editor/core/src/ui/menus/table-menu/InsertRightTableIcon.tsx deleted file mode 100644 index 1a65709694b..00000000000 --- a/packages/editor/core/src/ui/menus/table-menu/InsertRightTableIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -const InsertRightTableIcon = (props: any) => ( - - - -); - -export default InsertRightTableIcon; diff --git a/packages/editor/core/src/ui/menus/table-menu/InsertTopTableIcon.tsx b/packages/editor/core/src/ui/menus/table-menu/InsertTopTableIcon.tsx deleted file mode 100644 index 8f04f4f6126..00000000000 --- a/packages/editor/core/src/ui/menus/table-menu/InsertTopTableIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const InsertTopTableIcon = (props: any) => ( - - - -); -export default InsertTopTableIcon; diff --git a/packages/editor/core/src/ui/menus/table-menu/tooltip.tsx b/packages/editor/core/src/ui/menus/table-menu/tooltip.tsx deleted file mode 100644 index f29d8a49177..00000000000 --- a/packages/editor/core/src/ui/menus/table-menu/tooltip.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; - -// next-themes -import { useTheme } from "next-themes"; -// tooltip2 -import { Tooltip2 } from "@blueprintjs/popover2"; - -type Props = { - tooltipHeading?: string; - tooltipContent: string | React.ReactNode; - position?: - | "top" - | "right" - | "bottom" - | "left" - | "auto" - | "auto-end" - | "auto-start" - | "bottom-left" - | "bottom-right" - | "left-bottom" - | "left-top" - | "right-bottom" - | "right-top" - | "top-left" - | "top-right"; - children: JSX.Element; - disabled?: boolean; - className?: string; - openDelay?: number; - closeDelay?: number; -}; - -export const Tooltip: React.FC = ({ - tooltipHeading, - tooltipContent, - position = "top", - children, - disabled = false, - className = "", - openDelay = 200, - closeDelay, -}) => { - const { theme } = useTheme(); - - return ( - - {tooltipHeading && ( -
- {tooltipHeading} -
- )} - {tooltipContent} -
- } - position={position} - renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) => - React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props }) - } - /> - ); -}; From 21db26c2e6b9ee1ea323cb8aa9516e34b2f75d44 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:02:19 +0530 Subject: [PATCH 6/7] solved lint error of trailing spaces --- web/components/issues/description-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 886e52c5f02..8c6a75d3013 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -37,7 +37,7 @@ export const IssueDescriptionForm: FC = (props) => { const [characterLimit, setCharacterLimit] = useState(false); const { setShowAlert } = useReloadConfirmations(); - + const editorSuggestion = useEditorSuggestions(); const { From 95ec42a76015e99fe035d48fa397f4f41ccc6858 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:15:43 +0530 Subject: [PATCH 7/7] added plane/ui dependency for tooltips --- packages/editor/lite-text-editor/package.json | 1 + .../editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/editor/lite-text-editor/package.json b/packages/editor/lite-text-editor/package.json index 3b6cd720b78..52f27fb291c 100644 --- a/packages/editor/lite-text-editor/package.json +++ b/packages/editor/lite-text-editor/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@plane/editor-core": "*", + "@plane/ui": "*", "@tiptap/extension-list-item": "^2.1.11", "class-variance-authority": "^0.7.0", "clsx": "^1.2.1", diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx index cf0d78688fa..a4fb0479c17 100644 --- a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx +++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/index.tsx @@ -14,8 +14,8 @@ import { TableItem, UnderLineItem, } from "@plane/editor-core"; -import { Tooltip } from "../../tooltip"; -import { UploadImage } from "../.."; +import { Tooltip } from "@plane/ui"; +import { UploadImage } from "../../"; export interface BubbleMenuItem { name: string;