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/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/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/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 })
- }
- />
- );
-};
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/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/index.tsx b/packages/editor/lite-text-editor/src/ui/index.tsx
index ef321d511bf..26df0802561 100644
--- a/packages/editor/lite-text-editor/src/ui/index.tsx
+++ b/packages/editor/lite-text-editor/src/ui/index.tsx
@@ -47,6 +47,7 @@ interface ILiteTextEditor {
}[];
};
onEnterKeyPress?: (e?: any) => void;
+ cancelUploadImage?: () => any;
mentionHighlights?: string[];
mentionSuggestions?: IMentionSuggestion[];
submitButton?: React.ReactNode;
@@ -64,6 +65,7 @@ interface EditorHandle {
const LiteTextEditor = (props: LiteTextEditorProps) => {
const {
onChange,
+ cancelUploadImage,
debouncedUpdatesEnabled,
setIsSubmitting,
setShouldShowAlert,
@@ -84,6 +86,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
const editor = useEditor({
onChange,
+ cancelUploadImage,
debouncedUpdatesEnabled,
setIsSubmitting,
setShouldShowAlert,
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;
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/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/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/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx
index a468580f4e5..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;
@@ -134,6 +137,7 @@ export const CreateInboxIssueModal: React.FC = observer((props) => {
control={control}
render={({ field: { value, onChange } }) => (
= observer((props) => {
onChange={(description, description_html: string) => {
onChange(description_html);
}}
+ mentionSuggestions={editorSuggestion.mentionSuggestions}
+ mentionHighlights={editorSuggestion.mentionHighlights}
/>
)}
/>
diff --git a/web/components/issues/comment/add-comment.tsx b/web/components/issues/comment/add-comment.tsx
index 28c986bc7ce..ee7805ef743 100644
--- a/web/components/issues/comment/add-comment.tsx
+++ b/web/components/issues/comment/add-comment.tsx
@@ -84,6 +84,7 @@ export const AddComment: React.FC = ({ disabled = false, onSubmit, showAc
render={({ field: { onChange: onCommentChange, value: commentValue } }) => (
= ({
= (props) => {
control={control}
render={({ field: { value, onChange } }) => (
= (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 && (
{
+ 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/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));