diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b25a791d0..73d69fb2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,8 +8,8 @@ Before submitting a new issue, please search the [issues](https://github.com/mak While we want to fix all the [issues](https://github.com/makeplane/plane/issues), before fixing a bug we need to be able to reproduce and confirm it. Please provide us with a minimal reproduction scenario using a repository or [Gist](https://gist.github.com/). Having a live, reproducible scenario gives us the information without asking questions back & forth with additional questions like: -- 3rd-party libraries being used and their versions -- a use-case that fails +- 3rd-party libraries being used and their versions +- a use-case that fails Without said minimal reproduction, we won't be able to investigate all [issues](https://github.com/makeplane/plane/issues), and the issue might not be resolved. @@ -19,10 +19,10 @@ You can open a new issue with this [issue form](https://github.com/makeplane/pla ### Requirements -- Node.js version v16.18.0 -- Python version 3.8+ -- Postgres version v14 -- Redis version v6.2.7 +- Node.js version v16.18.0 +- Python version 3.8+ +- Postgres version v14 +- Redis version v6.2.7 ### Setup the project @@ -81,8 +81,8 @@ If you would like to _implement_ it, an issue with your proposal must be submitt To ensure consistency throughout the source code, please keep these rules in mind as you are working: -- All features or bug fixes must be tested by one or more specs (unit-tests). -- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier. +- All features or bug fixes must be tested by one or more specs (unit-tests). +- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier. ## Need help? Questions and suggestions @@ -90,11 +90,11 @@ Questions, suggestions, and thoughts are most welcome. We can also be reached in ## Ways to contribute -- Try Plane Cloud and the self hosting platform and give feedback -- Add new integrations -- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose) -- Share your thoughts and suggestions with us -- Help create tutorials and blog posts -- Request a feature by submitting a proposal -- Report a bug -- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations. +- Try Plane Cloud and the self hosting platform and give feedback +- Add new integrations +- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose) +- Share your thoughts and suggestions with us +- Help create tutorials and blog posts +- Request a feature by submitting a proposal +- Report a bug +- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations. diff --git a/ENV_SETUP.md b/ENV_SETUP.md index 6796c3db6..23faf83f7 100644 --- a/ENV_SETUP.md +++ b/ENV_SETUP.md @@ -1,8 +1,10 @@ # Environment Variables + ​ -Environment variables are distributed in various files. Please refer them carefully. +Environment variables are distributed in various files. Please refer them carefully. ## {PROJECT_FOLDER}/.env + File is available in the project root folder​ ``` @@ -41,25 +43,37 @@ USE_MINIO=1 # Nginx Configuration NGINX_PORT=80 ``` + ​ + ## {PROJECT_FOLDER}/web/.env.example + ​ + ``` # Enable/Disable OAUTH - default 0 for selfhosted instance NEXT_PUBLIC_ENABLE_OAUTH=0 # Public boards deploy URL NEXT_PUBLIC_DEPLOY_URL="http://localhost/spaces" ``` + ​ + ## {PROJECT_FOLDER}/spaces/.env.example + ​ + ``` # Flag to toggle OAuth NEXT_PUBLIC_ENABLE_OAUTH=0 ``` + ​ + ## {PROJECT_FOLDER}/apiserver/.env + ​ + ``` # Backend # Debug value for api server use it as 0 for production use @@ -126,7 +140,9 @@ ENABLE_SIGNUP="1" # Email Redirection URL WEB_URL="http://localhost" ``` + ## Updates​ + - The environment variable NEXT_PUBLIC_API_BASE_URL has been removed from both the web and space projects. - The naming convention for containers and images has been updated. - The plane-worker image will no longer be maintained, as it has been merged with plane-backend. diff --git a/packages/editor/core/Readme.md b/packages/editor/core/Readme.md index 56d1a502c..aafda7008 100644 --- a/packages/editor/core/Readme.md +++ b/packages/editor/core/Readme.md @@ -19,27 +19,27 @@ This allows for extensive customization and flexibility in the Editors created u 1. useEditor - A hook that you can use to extend the Plane editor. - | Prop | Type | Description | - | --- | --- | --- | - | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | - | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | - | `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | - | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | - | `value` | `html string` | The initial content of the editor. | - | `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | - | `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | - | `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | - | `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert in case of content not being "saved". | - | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | + | Prop | Type | Description | + | ------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | + | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | + | `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | + | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | + | `value` | `html string` | The initial content of the editor. | + | `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | + | `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | + | `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | + | `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert in case of content not being "saved". | + | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | 2. useReadOnlyEditor - A hook that can be used to extend a Read Only instance of the core editor. - | Prop | Type | Description | - | --- | --- | --- | - | `value` | `string` | The initial content of the editor. | - | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | - | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | - | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | + | Prop | Type | Description | + | -------------- | ------------- | ------------------------------------------------------------------------------------------ | + | `value` | `string` | The initial content of the editor. | + | `forwardedRef` | `any` | Pass this in whenever you want to control the editor's state from an external component | + | `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | + | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object | 3. Items and Commands - H1, H2, H3, task list, quote, code block, etc's methods. @@ -51,7 +51,11 @@ This allows for extensive customization and flexibility in the Editors created u 5. Extending with Custom Styles ```ts -const customEditorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); +const customEditorClassNames = getEditorClassNames({ + noBorder, + borderOnFocus, + customClassName, +}); ``` ## Core features diff --git a/packages/editor/core/src/lib/editor-commands.ts b/packages/editor/core/src/lib/editor-commands.ts index 497a63ca6..8f9e36350 100644 --- a/packages/editor/core/src/lib/editor-commands.ts +++ b/packages/editor/core/src/lib/editor-commands.ts @@ -3,18 +3,36 @@ import { UploadImage } from "../types/upload-image"; import { startImageUpload } from "../ui/plugins/upload-image"; export const toggleHeadingOne = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); - else editor.chain().focus().toggleHeading({ level: 1 }).run() + if (range) + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 1 }) + .run(); + else editor.chain().focus().toggleHeading({ level: 1 }).run(); }; export const toggleHeadingTwo = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); - else editor.chain().focus().toggleHeading({ level: 2 }).run() + if (range) + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 2 }) + .run(); + else editor.chain().focus().toggleHeading({ level: 2 }).run(); }; export const toggleHeadingThree = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); - else editor.chain().focus().toggleHeading({ level: 3 }).run() + if (range) + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 3 }) + .run(); + else editor.chain().focus().toggleHeading({ level: 3 }).run(); }; export const toggleBold = (editor: Editor, range?: Range) => { @@ -37,7 +55,8 @@ export const toggleCode = (editor: Editor, range?: Range) => { else editor.chain().focus().toggleCode().run(); }; export const toggleOrderedList = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + if (range) + editor.chain().focus().deleteRange(range).toggleOrderedList().run(); else editor.chain().focus().toggleOrderedList().run(); }; @@ -48,7 +67,7 @@ export const toggleBulletList = (editor: Editor, range?: Range) => { export const toggleTaskList = (editor: Editor, range?: Range) => { if (range) editor.chain().focus().deleteRange(range).toggleTaskList().run(); - else editor.chain().focus().toggleTaskList().run() + else editor.chain().focus().toggleTaskList().run(); }; export const toggleStrike = (editor: Editor, range?: Range) => { @@ -57,13 +76,37 @@ export const toggleStrike = (editor: Editor, range?: Range) => { }; export const toggleBlockquote = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(); - else editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(); + if (range) + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .toggleBlockquote() + .run(); + else + editor + .chain() + .focus() + .toggleNode("paragraph", "paragraph") + .toggleBlockquote() + .run(); }; export const insertTableCommand = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); - else editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); + if (range) + editor + .chain() + .focus() + .deleteRange(range) + .insertTable({ rows: 3, cols: 3, withHeaderRow: true }) + .run(); + else + editor + .chain() + .focus() + .insertTable({ rows: 3, cols: 3, withHeaderRow: true }) + .run(); }; export const unsetLinkEditor = (editor: Editor) => { @@ -74,7 +117,14 @@ export const setLinkEditor = (editor: Editor, url: string) => { editor.chain().focus().setLink({ href: url }).run(); }; -export const insertImageCommand = (editor: Editor, uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void, range?: Range) => { +export const insertImageCommand = ( + editor: Editor, + uploadFile: UploadImage, + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void, + range?: Range, +) => { if (range) editor.chain().focus().deleteRange(range).run(); const input = document.createElement("input"); input.type = "file"; @@ -88,4 +138,3 @@ export const insertImageCommand = (editor: Editor, uploadFile: UploadImage, setI }; input.click(); }; - diff --git a/packages/editor/core/src/lib/utils.ts b/packages/editor/core/src/lib/utils.ts index 484674780..f426b70b7 100644 --- a/packages/editor/core/src/lib/utils.ts +++ b/packages/editor/core/src/lib/utils.ts @@ -6,19 +6,24 @@ interface EditorClassNames { customClassName?: string; } -export const getEditorClassNames = ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) => cn( - 'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md', - noBorder ? '' : 'border border-custom-border-200', - borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0', - customClassName -); +export const getEditorClassNames = ({ + noBorder, + borderOnFocus, + customClassName, +}: EditorClassNames) => + cn( + "relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md", + noBorder ? "" : "border border-custom-border-200", + borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0", + customClassName, + ); export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export const findTableAncestor = ( - node: Node | null + node: Node | null, ): HTMLTableElement | null => { while (node !== null && node.nodeName !== "TABLE") { node = node.parentNode; @@ -27,10 +32,10 @@ export const findTableAncestor = ( }; export const getTrimmedHTML = (html: string) => { - html = html.replace(/^(

<\/p>)+/, ''); - html = html.replace(/(

<\/p>)+$/, ''); + html = html.replace(/^(

<\/p>)+/, ""); + html = html.replace(/(

<\/p>)+$/, ""); return html; -} +}; export const isValidHttpUrl = (string: string): boolean => { let url: URL; @@ -42,4 +47,4 @@ export const isValidHttpUrl = (string: string): boolean => { } return url.protocol === "http:" || url.protocol === "https:"; -} +}; diff --git a/packages/editor/core/src/types/mention-suggestion.ts b/packages/editor/core/src/types/mention-suggestion.ts index 9c9ab7606..dcaa3148d 100644 --- a/packages/editor/core/src/types/mention-suggestion.ts +++ b/packages/editor/core/src/types/mention-suggestion.ts @@ -1,10 +1,10 @@ export type IMentionSuggestion = { - id: string; - type: string; - avatar: string; - title: string; - subtitle: string; - redirect_uri: string; -} + id: string; + type: string; + avatar: string; + title: string; + subtitle: string; + redirect_uri: string; +}; -export type IMentionHighlight = string \ No newline at end of file +export type IMentionHighlight = string; diff --git a/packages/editor/core/src/ui/components/editor-content.tsx b/packages/editor/core/src/ui/components/editor-content.tsx index d0531da01..830b87d9c 100644 --- a/packages/editor/core/src/ui/components/editor-content.tsx +++ b/packages/editor/core/src/ui/components/editor-content.tsx @@ -8,10 +8,16 @@ interface EditorContentProps { children?: ReactNode; } -export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = '', children }: EditorContentProps) => ( +export const EditorContentWrapper = ({ + editor, + editorContentCustomClassNames = "", + children, +}: EditorContentProps) => (

- {(editor?.isActive("image") && editor?.isEditable) && } + {editor?.isActive("image") && editor?.isEditable && ( + + )} {children}
); diff --git a/packages/editor/core/src/ui/extensions/image/image-resize.tsx b/packages/editor/core/src/ui/extensions/image/image-resize.tsx index 5e86475cf..2545c7e44 100644 --- a/packages/editor/core/src/ui/extensions/image/image-resize.tsx +++ b/packages/editor/core/src/ui/extensions/image/image-resize.tsx @@ -3,7 +3,9 @@ import Moveable from "react-moveable"; export const ImageResizer = ({ editor }: { editor: Editor }) => { const updateMediaSize = () => { - const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement; + const imageInfo = document.querySelector( + ".ProseMirror-selectednode", + ) as HTMLImageElement; if (imageInfo) { const selection = editor.state.selection; editor.commands.setImage({ diff --git a/packages/editor/core/src/ui/extensions/table/table-cell/index.ts b/packages/editor/core/src/ui/extensions/table/table-cell/index.ts index b39fe7104..fb2183381 100644 --- a/packages/editor/core/src/ui/extensions/table/table-cell/index.ts +++ b/packages/editor/core/src/ui/extensions/table/table-cell/index.ts @@ -1 +1 @@ -export { default as default } from "./table-cell" +export { default as default } from "./table-cell"; diff --git a/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts b/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts index ac43875da..1d3e57af9 100644 --- a/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts +++ b/packages/editor/core/src/ui/extensions/table/table-cell/table-cell.ts @@ -1,7 +1,7 @@ -import { mergeAttributes, Node } from "@tiptap/core" +import { mergeAttributes, Node } from "@tiptap/core"; export interface TableCellOptions { - HTMLAttributes: Record + HTMLAttributes: Record; } export default Node.create({ @@ -9,8 +9,8 @@ export default Node.create({ addOptions() { return { - HTMLAttributes: {} - } + HTMLAttributes: {}, + }; }, content: "paragraph+", @@ -18,24 +18,24 @@ export default Node.create({ addAttributes() { return { colspan: { - default: 1 + default: 1, }, rowspan: { - default: 1 + default: 1, }, colwidth: { default: null, parseHTML: (element) => { - const colwidth = element.getAttribute("colwidth") - const value = colwidth ? [parseInt(colwidth, 10)] : null + const colwidth = element.getAttribute("colwidth"); + const value = colwidth ? [parseInt(colwidth, 10)] : null; - return value - } + return value; + }, }, background: { - default: "none" - } - } + default: "none", + }, + }; }, tableRole: "cell", @@ -43,16 +43,16 @@ export default Node.create({ isolating: true, parseHTML() { - return [{ tag: "td" }] + return [{ tag: "td" }]; }, renderHTML({ node, HTMLAttributes }) { return [ "td", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { - style: `background-color: ${node.attrs.background}` + style: `background-color: ${node.attrs.background}`, }), - 0 - ] - } -}) + 0, + ]; + }, +}); diff --git a/packages/editor/core/src/ui/extensions/table/table-header/index.ts b/packages/editor/core/src/ui/extensions/table/table-header/index.ts index 57137dedd..cb036c505 100644 --- a/packages/editor/core/src/ui/extensions/table/table-header/index.ts +++ b/packages/editor/core/src/ui/extensions/table/table-header/index.ts @@ -1 +1 @@ -export { default as default } from "./table-header" +export { default as default } from "./table-header"; diff --git a/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts b/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts index 712ca65f0..0148f1a6f 100644 --- a/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts +++ b/packages/editor/core/src/ui/extensions/table/table-header/table-header.ts @@ -1,15 +1,15 @@ -import { mergeAttributes, Node } from "@tiptap/core" +import { mergeAttributes, Node } from "@tiptap/core"; export interface TableHeaderOptions { - HTMLAttributes: Record + HTMLAttributes: Record; } export default Node.create({ name: "tableHeader", addOptions() { return { - HTMLAttributes: {} - } + HTMLAttributes: {}, + }; }, content: "paragraph+", @@ -17,24 +17,24 @@ export default Node.create({ addAttributes() { return { colspan: { - default: 1 + default: 1, }, rowspan: { - default: 1 + default: 1, }, colwidth: { default: null, parseHTML: (element) => { - const colwidth = element.getAttribute("colwidth") - const value = colwidth ? [parseInt(colwidth, 10)] : null + const colwidth = element.getAttribute("colwidth"); + const value = colwidth ? [parseInt(colwidth, 10)] : null; - return value - } + return value; + }, }, background: { - default: "rgb(var(--color-primary-100))" - } - } + default: "rgb(var(--color-primary-100))", + }, + }; }, tableRole: "header_cell", @@ -42,16 +42,16 @@ export default Node.create({ isolating: true, parseHTML() { - return [{ tag: "th" }] + return [{ tag: "th" }]; }, renderHTML({ node, HTMLAttributes }) { return [ "th", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { - style: `background-color: ${node.attrs.background}` + style: `background-color: ${node.attrs.background}`, }), - 0 - ] - } -}) + 0, + ]; + }, +}); diff --git a/packages/editor/core/src/ui/extensions/table/table-row/index.ts b/packages/editor/core/src/ui/extensions/table/table-row/index.ts index 9ecc2c0ae..8c6eb55aa 100644 --- a/packages/editor/core/src/ui/extensions/table/table-row/index.ts +++ b/packages/editor/core/src/ui/extensions/table/table-row/index.ts @@ -1 +1 @@ -export { default as default } from "./table-row" +export { default as default } from "./table-row"; diff --git a/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts b/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts index e922e7fa1..1b576623b 100644 --- a/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts +++ b/packages/editor/core/src/ui/extensions/table/table-row/table-row.ts @@ -1,31 +1,31 @@ -import { mergeAttributes, Node } from "@tiptap/core" +import { mergeAttributes, Node } from "@tiptap/core"; export interface TableRowOptions { - HTMLAttributes: Record + HTMLAttributes: Record; } export default Node.create({ - name: "tableRow", + name: "tableRow", - addOptions() { - return { - HTMLAttributes: {} - } - }, + addOptions() { + return { + HTMLAttributes: {}, + }; + }, - content: "(tableCell | tableHeader)*", + content: "(tableCell | tableHeader)*", - tableRole: "row", + tableRole: "row", - parseHTML() { - return [{ tag: "tr" }] - }, + parseHTML() { + return [{ tag: "tr" }]; + }, - renderHTML({ HTMLAttributes }) { - return [ - "tr", - mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), - 0 - ] - } -}) + renderHTML({ HTMLAttributes }) { + return [ + "tr", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0, + ]; + }, +}); diff --git a/packages/editor/core/src/ui/extensions/table/table/icons.ts b/packages/editor/core/src/ui/extensions/table/table/icons.ts index d3159d4aa..eda520759 100644 --- a/packages/editor/core/src/ui/extensions/table/table/icons.ts +++ b/packages/editor/core/src/ui/extensions/table/table/icons.ts @@ -38,7 +38,7 @@ const icons = { /> `, - insertBottomTableIcon:` - resizable: boolean - handleWidth: number - cellMinWidth: number - lastColumnResizable: boolean - allowTableNodeSelection: boolean + HTMLAttributes: Record; + resizable: boolean; + handleWidth: number; + cellMinWidth: number; + lastColumnResizable: boolean; + allowTableNodeSelection: boolean; } declare module "@tiptap/core" { - interface Commands { - table: { - insertTable: (options?: { - rows?: number - cols?: number - withHeaderRow?: boolean - }) => ReturnType - addColumnBefore: () => ReturnType - addColumnAfter: () => ReturnType - deleteColumn: () => ReturnType - addRowBefore: () => ReturnType - addRowAfter: () => ReturnType - deleteRow: () => ReturnType - deleteTable: () => ReturnType - mergeCells: () => ReturnType - splitCell: () => ReturnType - toggleHeaderColumn: () => ReturnType - toggleHeaderRow: () => ReturnType - toggleHeaderCell: () => ReturnType - mergeOrSplit: () => ReturnType - setCellAttribute: (name: string, value: any) => ReturnType - goToNextCell: () => ReturnType - goToPreviousCell: () => ReturnType - fixTables: () => ReturnType - setCellSelection: (position: { - anchorCell: number - headCell?: number - }) => ReturnType - } - } + interface Commands { + table: { + insertTable: (options?: { + rows?: number; + cols?: number; + withHeaderRow?: boolean; + }) => ReturnType; + addColumnBefore: () => ReturnType; + addColumnAfter: () => ReturnType; + deleteColumn: () => ReturnType; + addRowBefore: () => ReturnType; + addRowAfter: () => ReturnType; + deleteRow: () => ReturnType; + deleteTable: () => ReturnType; + mergeCells: () => ReturnType; + splitCell: () => ReturnType; + toggleHeaderColumn: () => ReturnType; + toggleHeaderRow: () => ReturnType; + toggleHeaderCell: () => ReturnType; + mergeOrSplit: () => ReturnType; + setCellAttribute: (name: string, value: any) => ReturnType; + goToNextCell: () => ReturnType; + goToPreviousCell: () => ReturnType; + fixTables: () => ReturnType; + setCellSelection: (position: { + anchorCell: number; + headCell?: number; + }) => ReturnType; + }; + } - interface NodeConfig { - tableRole?: - | string - | ((this: { - name: string - options: Options - storage: Storage - parent: ParentConfig>["tableRole"] - }) => string) - } + interface NodeConfig { + tableRole?: + | string + | ((this: { + name: string; + options: Options; + storage: Storage; + parent: ParentConfig>["tableRole"]; + }) => string); + } } export default Node.create({ - name: "table", + name: "table", - addOptions() { - return { - HTMLAttributes: {}, - resizable: true, - handleWidth: 5, - cellMinWidth: 100, - lastColumnResizable: true, - allowTableNodeSelection: true - } - }, + addOptions() { + return { + HTMLAttributes: {}, + resizable: true, + handleWidth: 5, + cellMinWidth: 100, + lastColumnResizable: true, + allowTableNodeSelection: true, + }; + }, - content: "tableRow+", + content: "tableRow+", - tableRole: "table", + tableRole: "table", - isolating: true, + isolating: true, - group: "block", + group: "block", - allowGapCursor: false, + allowGapCursor: false, - parseHTML() { - return [{ tag: "table" }] - }, + parseHTML() { + return [{ tag: "table" }]; + }, - renderHTML({ HTMLAttributes }) { - return [ - "table", - mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), - ["tbody", 0] - ] - }, + renderHTML({ HTMLAttributes }) { + return [ + "table", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + ["tbody", 0], + ]; + }, - addCommands() { - return { - insertTable: - ({ rows = 3, cols = 3, withHeaderRow = true} = {}) => - ({ tr, dispatch, editor }) => { - const node = createTable( - editor.schema, - rows, - cols, - withHeaderRow - ) + addCommands() { + return { + insertTable: + ({ rows = 3, cols = 3, withHeaderRow = true } = {}) => + ({ tr, dispatch, editor }) => { + const node = createTable(editor.schema, rows, cols, withHeaderRow); - if (dispatch) { - const offset = tr.selection.anchor + 1 + if (dispatch) { + const offset = tr.selection.anchor + 1; - tr.replaceSelectionWith(node) - .scrollIntoView() - .setSelection( - TextSelection.near(tr.doc.resolve(offset)) - ) - } + tr.replaceSelectionWith(node) + .scrollIntoView() + .setSelection(TextSelection.near(tr.doc.resolve(offset))); + } - return true - }, - addColumnBefore: - () => - ({ state, dispatch }) => addColumnBefore(state, dispatch), - addColumnAfter: - () => - ({ state, dispatch }) => addColumnAfter(state, dispatch), - deleteColumn: - () => - ({ state, dispatch }) => deleteColumn(state, dispatch), - addRowBefore: - () => - ({ state, dispatch }) => addRowBefore(state, dispatch), - addRowAfter: - () => - ({ state, dispatch }) => addRowAfter(state, dispatch), - deleteRow: - () => - ({ state, dispatch }) => deleteRow(state, dispatch), - deleteTable: - () => - ({ state, dispatch }) => deleteTable(state, dispatch), - mergeCells: - () => - ({ state, dispatch }) => mergeCells(state, dispatch), - splitCell: - () => - ({ state, dispatch }) => splitCell(state, dispatch), - toggleHeaderColumn: - () => - ({ state, dispatch }) => toggleHeader("column")(state, dispatch), - toggleHeaderRow: - () => - ({ state, dispatch }) => toggleHeader("row")(state, dispatch), - toggleHeaderCell: - () => - ({ state, dispatch }) => toggleHeaderCell(state, dispatch), - mergeOrSplit: - () => - ({ state, dispatch }) => { - if (mergeCells(state, dispatch)) { - return true - } + return true; + }, + addColumnBefore: + () => + ({ state, dispatch }) => + addColumnBefore(state, dispatch), + addColumnAfter: + () => + ({ state, dispatch }) => + addColumnAfter(state, dispatch), + deleteColumn: + () => + ({ state, dispatch }) => + deleteColumn(state, dispatch), + addRowBefore: + () => + ({ state, dispatch }) => + addRowBefore(state, dispatch), + addRowAfter: + () => + ({ state, dispatch }) => + addRowAfter(state, dispatch), + deleteRow: + () => + ({ state, dispatch }) => + deleteRow(state, dispatch), + deleteTable: + () => + ({ state, dispatch }) => + deleteTable(state, dispatch), + mergeCells: + () => + ({ state, dispatch }) => + mergeCells(state, dispatch), + splitCell: + () => + ({ state, dispatch }) => + splitCell(state, dispatch), + toggleHeaderColumn: + () => + ({ state, dispatch }) => + toggleHeader("column")(state, dispatch), + toggleHeaderRow: + () => + ({ state, dispatch }) => + toggleHeader("row")(state, dispatch), + toggleHeaderCell: + () => + ({ state, dispatch }) => + toggleHeaderCell(state, dispatch), + mergeOrSplit: + () => + ({ state, dispatch }) => { + if (mergeCells(state, dispatch)) { + return true; + } - return splitCell(state, dispatch) - }, - setCellAttribute: - (name, value) => - ({ state, dispatch }) => setCellAttr(name, value)(state, dispatch), - goToNextCell: - () => - ({ state, dispatch }) => goToNextCell(1)(state, dispatch), - goToPreviousCell: - () => - ({ state, dispatch }) => goToNextCell(-1)(state, dispatch), - fixTables: - () => - ({ state, dispatch }) => { - if (dispatch) { - fixTables(state) - } + return splitCell(state, dispatch); + }, + setCellAttribute: + (name, value) => + ({ state, dispatch }) => + setCellAttr(name, value)(state, dispatch), + goToNextCell: + () => + ({ state, dispatch }) => + goToNextCell(1)(state, dispatch), + goToPreviousCell: + () => + ({ state, dispatch }) => + goToNextCell(-1)(state, dispatch), + fixTables: + () => + ({ state, dispatch }) => { + if (dispatch) { + fixTables(state); + } - return true - }, - setCellSelection: - (position) => - ({ tr, dispatch }) => { - if (dispatch) { - const selection = CellSelection.create( - tr.doc, - position.anchorCell, - position.headCell - ) + return true; + }, + setCellSelection: + (position) => + ({ tr, dispatch }) => { + if (dispatch) { + const selection = CellSelection.create( + tr.doc, + position.anchorCell, + position.headCell, + ); - // @ts-ignore - tr.setSelection(selection) - } + // @ts-ignore + tr.setSelection(selection); + } - return true - } - } - }, + return true; + }, + }; + }, - addKeyboardShortcuts() { - return { - Tab: () => { - if (this.editor.commands.goToNextCell()) { - return true - } - - if (!this.editor.can().addRowAfter()) { - return false - } - - return this.editor.chain().addRowAfter().goToNextCell().run() - }, - "Shift-Tab": () => this.editor.commands.goToPreviousCell(), - Backspace: deleteTableWhenAllCellsSelected, - "Mod-Backspace": deleteTableWhenAllCellsSelected, - Delete: deleteTableWhenAllCellsSelected, - "Mod-Delete": deleteTableWhenAllCellsSelected - } - }, - - addNodeView() { - return ({ editor, getPos, node, decorations }) => { - const { cellMinWidth } = this.options - - return new TableView( - node, - cellMinWidth, - decorations, - editor, - getPos as () => number - ) - } - }, - - addProseMirrorPlugins() { - const isResizable = this.options.resizable && this.editor.isEditable - - const plugins = [ - tableEditing({ - allowTableNodeSelection: this.options.allowTableNodeSelection - }), - tableControls() - ] - - if (isResizable) { - plugins.unshift( - columnResizing({ - handleWidth: this.options.handleWidth, - cellMinWidth: this.options.cellMinWidth, - // View: TableView, - - // @ts-ignore - lastColumnResizable: this.options.lastColumnResizable - }) - ) + addKeyboardShortcuts() { + return { + Tab: () => { + if (this.editor.commands.goToNextCell()) { + return true; } - return plugins - }, - - extendNodeSchema(extension) { - const context = { - name: extension.name, - options: extension.options, - storage: extension.storage + if (!this.editor.can().addRowAfter()) { + return false; } - return { - tableRole: callOrReturn( - getExtensionField(extension, "tableRole", context) - ) - } + return this.editor.chain().addRowAfter().goToNextCell().run(); + }, + "Shift-Tab": () => this.editor.commands.goToPreviousCell(), + Backspace: deleteTableWhenAllCellsSelected, + "Mod-Backspace": deleteTableWhenAllCellsSelected, + Delete: deleteTableWhenAllCellsSelected, + "Mod-Delete": deleteTableWhenAllCellsSelected, + }; + }, + + addNodeView() { + return ({ editor, getPos, node, decorations }) => { + const { cellMinWidth } = this.options; + + return new TableView( + node, + cellMinWidth, + decorations, + editor, + getPos as () => number, + ); + }; + }, + + addProseMirrorPlugins() { + const isResizable = this.options.resizable && this.editor.isEditable; + + const plugins = [ + tableEditing({ + allowTableNodeSelection: this.options.allowTableNodeSelection, + }), + tableControls(), + ]; + + if (isResizable) { + plugins.unshift( + columnResizing({ + handleWidth: this.options.handleWidth, + cellMinWidth: this.options.cellMinWidth, + // View: TableView, + + // @ts-ignore + lastColumnResizable: this.options.lastColumnResizable, + }), + ); } -}) + + return plugins; + }, + + extendNodeSchema(extension) { + const context = { + name: extension.name, + options: extension.options, + storage: extension.storage, + }; + + return { + tableRole: callOrReturn( + getExtensionField(extension, "tableRole", context), + ), + }; + }, +}); diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts index a3d7f2da8..7811341e0 100644 --- a/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/create-cell.ts @@ -1,12 +1,12 @@ -import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model" +import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model"; export function createCell( - cellType: NodeType, - cellContent?: Fragment | ProsemirrorNode | Array + cellType: NodeType, + cellContent?: Fragment | ProsemirrorNode | Array, ): ProsemirrorNode | null | undefined { - if (cellContent) { - return cellType.createChecked(null, cellContent) - } + if (cellContent) { + return cellType.createChecked(null, cellContent); + } - return cellType.createAndFill() + return cellType.createAndFill(); } diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts index 75bf7cb41..5805ecf86 100644 --- a/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/create-table.ts @@ -1,45 +1,45 @@ -import { Fragment, Node as ProsemirrorNode, Schema } from "@tiptap/pm/model" +import { Fragment, Node as ProsemirrorNode, Schema } from "@tiptap/pm/model"; -import { createCell } from "./create-cell" -import { getTableNodeTypes } from "./get-table-node-types" +import { createCell } from "./create-cell"; +import { getTableNodeTypes } from "./get-table-node-types"; export function createTable( - schema: Schema, - rowsCount: number, - colsCount: number, - withHeaderRow: boolean, - cellContent?: Fragment | ProsemirrorNode | Array + schema: Schema, + rowsCount: number, + colsCount: number, + withHeaderRow: boolean, + cellContent?: Fragment | ProsemirrorNode | Array, ): ProsemirrorNode { - const types = getTableNodeTypes(schema) - const headerCells: ProsemirrorNode[] = [] - const cells: ProsemirrorNode[] = [] + const types = getTableNodeTypes(schema); + const headerCells: ProsemirrorNode[] = []; + const cells: ProsemirrorNode[] = []; - for (let index = 0; index < colsCount; index += 1) { - const cell = createCell(types.cell, cellContent) + for (let index = 0; index < colsCount; index += 1) { + const cell = createCell(types.cell, cellContent); - if (cell) { - cells.push(cell) - } - - if (withHeaderRow) { - const headerCell = createCell(types.header_cell, cellContent) - - if (headerCell) { - headerCells.push(headerCell) - } - } + if (cell) { + cells.push(cell); } - const rows: ProsemirrorNode[] = [] + if (withHeaderRow) { + const headerCell = createCell(types.header_cell, cellContent); - for (let index = 0; index < rowsCount; index += 1) { - rows.push( - types.row.createChecked( - null, - withHeaderRow && index === 0 ? headerCells : cells - ) - ) + if (headerCell) { + headerCells.push(headerCell); + } } + } - return types.table.createChecked(null, rows) + const rows: ProsemirrorNode[] = []; + + for (let index = 0; index < rowsCount; index += 1) { + rows.push( + types.row.createChecked( + null, + withHeaderRow && index === 0 ? headerCells : cells, + ), + ); + } + + return types.table.createChecked(null, rows); } diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts index dcb20b323..7fed53705 100644 --- a/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected.ts @@ -1,39 +1,42 @@ -import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core" +import { + findParentNodeClosestToPos, + KeyboardShortcutCommand, +} from "@tiptap/core"; -import { isCellSelection } from "./is-cell-selection" +import { isCellSelection } from "./is-cell-selection"; export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ - editor + editor, }) => { - const { selection } = editor.state + const { selection } = editor.state; - if (!isCellSelection(selection)) { - return false + if (!isCellSelection(selection)) { + return false; + } + + let cellCount = 0; + const table = findParentNodeClosestToPos( + selection.ranges[0].$from, + (node) => node.type.name === "table", + ); + + table?.node.descendants((node) => { + if (node.type.name === "table") { + return false; } - let cellCount = 0 - const table = findParentNodeClosestToPos( - selection.ranges[0].$from, - (node) => node.type.name === "table" - ) - - table?.node.descendants((node) => { - if (node.type.name === "table") { - return false - } - - if (["tableCell", "tableHeader"].includes(node.type.name)) { - cellCount += 1 - } - }) - - const allCellsSelected = cellCount === selection.ranges.length - - if (!allCellsSelected) { - return false + if (["tableCell", "tableHeader"].includes(node.type.name)) { + cellCount += 1; } + }); - editor.commands.deleteTable() + const allCellsSelected = cellCount === selection.ranges.length; - return true -} + if (!allCellsSelected) { + return false; + } + + editor.commands.deleteTable(); + + return true; +}; diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts index 293878cb0..28c322a1f 100644 --- a/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/get-table-node-types.ts @@ -1,21 +1,21 @@ -import { NodeType, Schema } from "prosemirror-model" +import { NodeType, Schema } from "prosemirror-model"; export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } { - if (schema.cached.tableNodeTypes) { - return schema.cached.tableNodeTypes + if (schema.cached.tableNodeTypes) { + return schema.cached.tableNodeTypes; + } + + const roles: { [key: string]: NodeType } = {}; + + Object.keys(schema.nodes).forEach((type) => { + const nodeType = schema.nodes[type]; + + if (nodeType.spec.tableRole) { + roles[nodeType.spec.tableRole] = nodeType; } + }); - const roles: { [key: string]: NodeType } = {} + schema.cached.tableNodeTypes = roles; - Object.keys(schema.nodes).forEach((type) => { - const nodeType = schema.nodes[type] - - if (nodeType.spec.tableRole) { - roles[nodeType.spec.tableRole] = nodeType - } - }) - - schema.cached.tableNodeTypes = roles - - return roles + return roles; } diff --git a/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts b/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts index 3c36bf055..28917a299 100644 --- a/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts +++ b/packages/editor/core/src/ui/extensions/table/table/utilities/is-cell-selection.ts @@ -1,5 +1,5 @@ -import { CellSelection } from "@tiptap/prosemirror-tables" +import { CellSelection } from "@tiptap/prosemirror-tables"; export function isCellSelection(value: unknown): value is CellSelection { - return value instanceof CellSelection + return value instanceof CellSelection; } diff --git a/packages/editor/core/src/ui/hooks/useEditor.tsx b/packages/editor/core/src/ui/hooks/useEditor.tsx index a0d55ce27..258da8652 100644 --- a/packages/editor/core/src/ui/hooks/useEditor.tsx +++ b/packages/editor/core/src/ui/hooks/useEditor.tsx @@ -95,4 +95,3 @@ export const useEditor = ({ return editor; }; - diff --git a/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx index 9243c2f4e..75ebddd3c 100644 --- a/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx +++ b/packages/editor/core/src/ui/hooks/useReadOnlyEditor.tsx @@ -7,7 +7,7 @@ import { } from "react"; import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions"; import { CoreReadOnlyEditorProps } from "../../ui/read-only/props"; -import { EditorProps } from '@tiptap/pm/view'; +import { EditorProps } from "@tiptap/pm/view"; import { IMentionSuggestion } from "../../types/mention-suggestion"; interface CustomReadOnlyEditorProps { @@ -19,7 +19,14 @@ interface CustomReadOnlyEditorProps { mentionSuggestions?: IMentionSuggestion[]; } -export const useReadOnlyEditor = ({ value, forwardedRef, extensions = [], editorProps = {}, mentionHighlights, mentionSuggestions}: CustomReadOnlyEditorProps) => { +export const useReadOnlyEditor = ({ + value, + forwardedRef, + extensions = [], + editorProps = {}, + mentionHighlights, + mentionSuggestions, +}: CustomReadOnlyEditorProps) => { const editor = useCustomEditor({ editable: false, content: @@ -28,7 +35,13 @@ export const useReadOnlyEditor = ({ value, forwardedRef, extensions = [], editor ...CoreReadOnlyEditorProps, ...editorProps, }, - extensions: [...CoreReadOnlyEditorExtensions({ mentionSuggestions: mentionSuggestions ?? [], mentionHighlights: mentionHighlights ?? []}), ...extensions], + extensions: [ + ...CoreReadOnlyEditorExtensions({ + mentionSuggestions: mentionSuggestions ?? [], + mentionHighlights: mentionHighlights ?? [], + }), + ...extensions, + ], }); const hasIntiliazedContent = useRef(false); diff --git a/packages/editor/core/src/ui/mentions/custom.tsx b/packages/editor/core/src/ui/mentions/custom.tsx index c3bfa3703..dc4ab5aad 100644 --- a/packages/editor/core/src/ui/mentions/custom.tsx +++ b/packages/editor/core/src/ui/mentions/custom.tsx @@ -1,11 +1,11 @@ -import { Mention, MentionOptions } from '@tiptap/extension-mention' -import { mergeAttributes } from '@tiptap/core' -import { ReactNodeViewRenderer } from '@tiptap/react' -import mentionNodeView from './mentionNodeView' -import { IMentionHighlight } from '../../types/mention-suggestion' +import { Mention, MentionOptions } from "@tiptap/extension-mention"; +import { mergeAttributes } from "@tiptap/core"; +import { ReactNodeViewRenderer } from "@tiptap/react"; +import mentionNodeView from "./mentionNodeView"; +import { IMentionHighlight } from "../../types/mention-suggestion"; export interface CustomMentionOptions extends MentionOptions { - mentionHighlights: IMentionHighlight[] - readonly?: boolean + mentionHighlights: IMentionHighlight[]; + readonly?: boolean; } export const CustomMention = Mention.extend({ @@ -21,35 +21,37 @@ export const CustomMention = Mention.extend({ default: null, }, self: { - default: false + default: false, }, redirect_uri: { - default: "/" - } - } + default: "/", + }, + }; }, addNodeView() { - return ReactNodeViewRenderer(mentionNodeView) + return ReactNodeViewRenderer(mentionNodeView); }, parseHTML() { - return [{ - tag: 'mention-component', - getAttrs: (node: string | HTMLElement) => { - if (typeof node === 'string') { - return null; - } - return { - id: node.getAttribute('data-mention-id') || '', - target: node.getAttribute('data-mention-target') || '', - label: node.innerText.slice(1) || '', - redirect_uri: node.getAttribute('redirect_uri') - } + return [ + { + tag: "mention-component", + getAttrs: (node: string | HTMLElement) => { + if (typeof node === "string") { + return null; + } + return { + id: node.getAttribute("data-mention-id") || "", + target: node.getAttribute("data-mention-target") || "", + label: node.innerText.slice(1) || "", + redirect_uri: node.getAttribute("redirect_uri"), + }; + }, }, - }] + ]; }, renderHTML({ HTMLAttributes }) { - return ['mention-component', mergeAttributes(HTMLAttributes)] + return ["mention-component", mergeAttributes(HTMLAttributes)]; }, -}) +}); diff --git a/packages/editor/core/src/ui/mentions/index.tsx b/packages/editor/core/src/ui/mentions/index.tsx index ba1a9ed0b..42ec92554 100644 --- a/packages/editor/core/src/ui/mentions/index.tsx +++ b/packages/editor/core/src/ui/mentions/index.tsx @@ -2,14 +2,21 @@ import suggestion from "./suggestion"; import { CustomMention } from "./custom"; -import { IMentionHighlight, IMentionSuggestion } from "../../types/mention-suggestion"; - -export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighlights: IMentionHighlight[], readonly) => CustomMention.configure({ - HTMLAttributes: { - 'class' : "mention", - }, - readonly: readonly, - mentionHighlights: mentionHighlights, - suggestion: suggestion(mentionSuggestions), -}) +import { + IMentionHighlight, + IMentionSuggestion, +} from "../../types/mention-suggestion"; +export const Mentions = ( + mentionSuggestions: IMentionSuggestion[], + mentionHighlights: IMentionHighlight[], + readonly, +) => + CustomMention.configure({ + HTMLAttributes: { + class: "mention", + }, + readonly: readonly, + mentionHighlights: mentionHighlights, + suggestion: suggestion(mentionSuggestions), + }); diff --git a/packages/editor/core/src/ui/mentions/suggestion.ts b/packages/editor/core/src/ui/mentions/suggestion.ts index b4bbc53a6..ce09cb092 100644 --- a/packages/editor/core/src/ui/mentions/suggestion.ts +++ b/packages/editor/core/src/ui/mentions/suggestion.ts @@ -1,12 +1,17 @@ -import { ReactRenderer } from '@tiptap/react' +import { ReactRenderer } from "@tiptap/react"; import { Editor } from "@tiptap/core"; -import tippy from 'tippy.js' +import tippy from "tippy.js"; -import MentionList from './MentionList' -import { IMentionSuggestion } from '../../types/mention-suggestion'; +import MentionList from "./MentionList"; +import { IMentionSuggestion } from "../../types/mention-suggestion"; const Suggestion = (suggestions: IMentionSuggestion[]) => ({ - items: ({ query }: { query: string }) => suggestions.filter(suggestion => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5), + items: ({ query }: { query: string }) => + suggestions + .filter((suggestion) => + suggestion.title.toLowerCase().startsWith(query.toLowerCase()), + ) + .slice(0, 5), render: () => { let reactRenderer: ReactRenderer | null = null; let popup: any | null = null; @@ -30,7 +35,7 @@ const Suggestion = (suggestions: IMentionSuggestion[]) => ({ }, onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => { - reactRenderer?.updateProps(props) + reactRenderer?.updateProps(props); popup && popup[0].setProps({ @@ -49,11 +54,10 @@ const Suggestion = (suggestions: IMentionSuggestion[]) => ({ }, onExit: () => { popup?.[0].destroy(); - reactRenderer?.destroy() + reactRenderer?.destroy(); }, - } + }; }, -}) - +}); export default Suggestion; diff --git a/packages/editor/core/src/ui/props.tsx b/packages/editor/core/src/ui/props.tsx index 8f002b76c..865e0d2c7 100644 --- a/packages/editor/core/src/ui/props.tsx +++ b/packages/editor/core/src/ui/props.tsx @@ -5,7 +5,9 @@ import { UploadImage } from "../types/upload-image"; export function CoreEditorProps( uploadFile: UploadImage, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void, ): EditorProps { return { attributes: { @@ -32,7 +34,11 @@ export function CoreEditorProps( } } } - if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) { + if ( + event.clipboardData && + event.clipboardData.files && + event.clipboardData.files[0] + ) { event.preventDefault(); const file = event.clipboardData.files[0]; const pos = view.state.selection.from; @@ -51,7 +57,12 @@ export function CoreEditorProps( } } } - if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { + if ( + !moved && + event.dataTransfer && + event.dataTransfer.files && + event.dataTransfer.files[0] + ) { event.preventDefault(); const file = event.dataTransfer.files[0]; const coordinates = view.posAtCoords({ @@ -59,7 +70,13 @@ export function CoreEditorProps( top: event.clientY, }); if (coordinates) { - startImageUpload(file, view, coordinates.pos - 1, uploadFile, setIsSubmitting); + startImageUpload( + file, + view, + coordinates.pos - 1, + uploadFile, + setIsSubmitting, + ); } return true; } diff --git a/packages/editor/core/src/ui/read-only/extensions.tsx b/packages/editor/core/src/ui/read-only/extensions.tsx index 8901d34c5..b8fc9bb95 100644 --- a/packages/editor/core/src/ui/read-only/extensions.tsx +++ b/packages/editor/core/src/ui/read-only/extensions.tsx @@ -18,9 +18,10 @@ import { isValidHttpUrl } from "../../lib/utils"; import { Mentions } from "../mentions"; import { IMentionSuggestion } from "../../types/mention-suggestion"; -export const CoreReadOnlyEditorExtensions = ( - mentionConfig: { mentionSuggestions: IMentionSuggestion[], mentionHighlights: string[] }, -) => [ +export const CoreReadOnlyEditorExtensions = (mentionConfig: { + mentionSuggestions: IMentionSuggestion[]; + mentionHighlights: string[]; +}) => [ StarterKit.configure({ bulletList: { HTMLAttributes: { @@ -57,41 +58,45 @@ export const CoreReadOnlyEditorExtensions = ( }, 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", - }, - }), - ReadOnlyImageExtension.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, true), - ]; + 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", + }, + }), + ReadOnlyImageExtension.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, + true, + ), +]; diff --git a/packages/editor/core/src/ui/read-only/props.tsx b/packages/editor/core/src/ui/read-only/props.tsx index 25db2b68c..79f9fcb0d 100644 --- a/packages/editor/core/src/ui/read-only/props.tsx +++ b/packages/editor/core/src/ui/read-only/props.tsx @@ -1,7 +1,6 @@ import { EditorProps } from "@tiptap/pm/view"; -export const CoreReadOnlyEditorProps: EditorProps = -{ +export const CoreReadOnlyEditorProps: EditorProps = { attributes: { class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`, }, diff --git a/packages/editor/lite-text-editor/Readme.md b/packages/editor/lite-text-editor/Readme.md index 948e2c34b..1f10f5ff4 100644 --- a/packages/editor/lite-text-editor/Readme.md +++ b/packages/editor/lite-text-editor/Readme.md @@ -10,25 +10,25 @@ The `@plane/lite-text-editor` package extends from the `editor-core` package, in `LiteTextEditor` & `LiteTextEditorWithRef` -- **Read Only Editor Instances**: We have added a really light weight *Read Only* Editor instance for the Lite editor types (with and without Ref) +- **Read Only Editor Instances**: We have added a really light weight _Read Only_ Editor instance for the Lite editor types (with and without Ref) `LiteReadOnlyEditor` &`LiteReadOnlyEditorWithRef` ## LiteTextEditor -| Prop | Type | Description | -| --- | --- | --- | -| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | -| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | -| `value` | `html string` | The initial content of the editor. | -| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press | -| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | -| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | -| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | -| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | -| `noBorder` | `boolean` | If set to true, the editor will not have a border. | -| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | -| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | -| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | +| Prop | Type | Description | +| ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | +| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | +| `value` | `html string` | The initial content of the editor. | +| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press | +| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | +| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | +| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | +| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | ### Usage @@ -36,62 +36,62 @@ The `@plane/lite-text-editor` package extends from the `editor-core` package, in ```tsx { - onChange(comment_html); - }} - /> + onEnterKeyPress={handleSubmit(handleCommentUpdate)} + uploadFile={fileService.getUploadFileFunction(workspaceSlug)} + deleteFile={fileService.deleteImage} + value={value} + debouncedUpdatesEnabled={false} + customClassName="min-h-[50px] p-3 shadow-sm" + onChange={(comment_json: Object, comment_html: string) => { + onChange(comment_html); + }} +/> ``` 2. Example of how to use the `LiteTextEditorWithRef` component ```tsx - const editorRef = useRef(null); +const editorRef = useRef(null); - // can use it to set the editor's value - editorRef.current?.setEditorValue(`${watch("description_html")}`); +// can use it to set the editor's value +editorRef.current?.setEditorValue(`${watch("description_html")}`); - // can use it to clear the editor - editorRef?.current?.clearEditor(); +// can use it to clear the editor +editorRef?.current?.clearEditor(); - return ( - { - onChange(comment_html); - }} - /> -) +return ( + { + onChange(comment_html); + }} + /> +); ``` ## LiteReadOnlyEditor -| Prop | Type | Description | -| --- | --- | --- | -| `value` | `html string` | The initial content of the editor. | -| `noBorder` | `boolean` | If set to true, the editor will not have a border. | -| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | -| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | -| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | +| Prop | Type | Description | +| ------------------------------- | ------------- | --------------------------------------------------------------------- | +| `value` | `html string` | The initial content of the editor. | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | ### Usage Here is an example of how to use the `RichReadOnlyEditor` component ```tsx - + ``` diff --git a/packages/editor/lite-text-editor/src/index.ts b/packages/editor/lite-text-editor/src/index.ts index 392928ccf..ba916e666 100644 --- a/packages/editor/lite-text-editor/src/index.ts +++ b/packages/editor/lite-text-editor/src/index.ts @@ -1,3 +1,3 @@ export { LiteTextEditor, LiteTextEditorWithRef } from "./ui"; export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "./ui/read-only"; -export type { IMentionSuggestion, IMentionHighlight } from "./ui" +export type { IMentionSuggestion, IMentionHighlight } from "./ui"; diff --git a/packages/editor/lite-text-editor/src/ui/index.tsx b/packages/editor/lite-text-editor/src/ui/index.tsx index 26df08025..e7decbcac 100644 --- a/packages/editor/lite-text-editor/src/ui/index.tsx +++ b/packages/editor/lite-text-editor/src/ui/index.tsx @@ -31,7 +31,7 @@ interface ILiteTextEditor { editorContentCustomClassNames?: string; onChange?: (json: any, html: string) => void; setIsSubmitting?: ( - isSubmitting: "submitting" | "submitted" | "saved" + isSubmitting: "submitting" | "submitted" | "saved", ) => void; setShouldShowAlert?: (showAlert: boolean) => void; forwardedRef?: any; @@ -129,7 +129,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => { }; const LiteTextEditorWithRef = React.forwardRef( - (props, ref) => + (props, ref) => , ); LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef"; diff --git a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx index c0006b3f2..60878f9bf 100644 --- a/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx +++ b/packages/editor/lite-text-editor/src/ui/menus/fixed-menu/icon.tsx @@ -6,8 +6,9 @@ type Props = { }; export const Icon: React.FC = ({ iconName, className = "" }) => ( - + {iconName} ); - diff --git a/packages/editor/rich-text-editor/Readme.md b/packages/editor/rich-text-editor/Readme.md index c8414f62d..44ed9ba5e 100644 --- a/packages/editor/rich-text-editor/Readme.md +++ b/packages/editor/rich-text-editor/Readme.md @@ -10,24 +10,24 @@ The `@plane/rich-text-editor` package extends from the `editor-core` package, in `RichTextEditor` & `RichTextEditorWithRef` -- **Read Only Editor Instances**: We have added a really light weight *Read Only* Editor instance for the Rich editor types (with and without Ref) +- **Read Only Editor Instances**: We have added a really light weight _Read Only_ Editor instance for the Rich editor types (with and without Ref) `RichReadOnlyEditor` &`RichReadOnlyEditorWithRef` ## RichTextEditor -| Prop | Type | Description | -| --- | --- | --- | -| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | -| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | -| `value` | `html string` | The initial content of the editor. | -| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | -| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | -| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | -| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | -| `noBorder` | `boolean` | If set to true, the editor will not have a border. | -| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | -| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | -| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | +| Prop | Type | Description | +| ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `uploadFile` | `(file: File) => Promise` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | +| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | +| `value` | `html string` | The initial content of the editor. | +| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. | +| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. | +| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. | +| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | ### Usage @@ -57,43 +57,47 @@ The `@plane/rich-text-editor` package extends from the `editor-core` package, in 2. Example of how to use the `RichTextEditorWithRef` component ```tsx - const editorRef = useRef(null); +const editorRef = useRef(null); - // can use it to set the editor's value - editorRef.current?.setEditorValue(`${watch("description_html")}`); +// can use it to set the editor's value +editorRef.current?.setEditorValue(`${watch("description_html")}`); - // can use it to clear the editor - editorRef?.current?.clearEditor(); +// can use it to clear the editor +editorRef?.current?.clearEditor(); - return ( { - onChange(description_html); - // custom stuff you want to do - } } />) +return ( + { + onChange(description_html); + // custom stuff you want to do + }} + /> +); ``` ## RichReadOnlyEditor -| Prop | Type | Description | -| --- | --- | --- | -| `value` | `html string` | The initial content of the editor. | -| `noBorder` | `boolean` | If set to true, the editor will not have a border. | -| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | -| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | -| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | +| Prop | Type | Description | +| ------------------------------- | ------------- | --------------------------------------------------------------------- | +| `value` | `html string` | The initial content of the editor. | +| `noBorder` | `boolean` | If set to true, the editor will not have a border. | +| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. | +| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. | +| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. | ### Usage Here is an example of how to use the `RichReadOnlyEditor` component ```tsx - + ``` diff --git a/packages/editor/rich-text-editor/src/index.ts b/packages/editor/rich-text-editor/src/index.ts index e296a6171..9ea7f9a39 100644 --- a/packages/editor/rich-text-editor/src/index.ts +++ b/packages/editor/rich-text-editor/src/index.ts @@ -2,4 +2,4 @@ import "./styles/github-dark.css"; export { RichTextEditor, RichTextEditorWithRef } from "./ui"; export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "./ui/read-only"; -export type { IMentionSuggestion, IMentionHighlight } from "./ui" +export type { IMentionSuggestion, IMentionHighlight } from "./ui"; diff --git a/packages/editor/rich-text-editor/src/ui/extensions/index.tsx b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx index f0f3bed34..a28982da3 100644 --- a/packages/editor/rich-text-editor/src/ui/extensions/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/extensions/index.tsx @@ -1,7 +1,7 @@ import HorizontalRule from "@tiptap/extension-horizontal-rule"; import Placeholder from "@tiptap/extension-placeholder"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; -import { common, createLowlight } from 'lowlight' +import { common, createLowlight } from "lowlight"; import { InputRule } from "@tiptap/core"; import ts from "highlight.js/lib/languages/typescript"; @@ -9,51 +9,53 @@ import ts from "highlight.js/lib/languages/typescript"; import SlashCommand from "./slash-command"; import { UploadImage } from "../"; -const lowlight = createLowlight(common) +const lowlight = createLowlight(common); lowlight.register("ts", ts); export const RichTextEditorExtensions = ( uploadFile: UploadImage, - setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void + setIsSubmitting?: ( + isSubmitting: "submitting" | "submitted" | "saved", + ) => void, ) => [ - HorizontalRule.extend({ - addInputRules() { - return [ - new InputRule({ - find: /^(?:---|—-|___\s|\*\*\*\s)$/, - handler: ({ state, range, commands }) => { - commands.splitBlock(); + HorizontalRule.extend({ + addInputRules() { + return [ + new InputRule({ + find: /^(?:---|—-|___\s|\*\*\*\s)$/, + handler: ({ state, range, commands }) => { + commands.splitBlock(); - const attributes = {}; - const { tr } = state; - const start = range.from; - const end = range.to; - // @ts-ignore - tr.replaceWith(start - 1, end, this.type.create(attributes)); - }, - }), - ]; - }, - }).configure({ - HTMLAttributes: { - class: "mb-6 border-t border-custom-border-300", - }, - }), - SlashCommand(uploadFile, setIsSubmitting), - CodeBlockLowlight.configure({ - lowlight, - }), - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return `Heading ${node.attrs.level}`; - } - if (node.type.name === "image" || node.type.name === "table") { - return ""; - } + const attributes = {}; + const { tr } = state; + const start = range.from; + const end = range.to; + // @ts-ignore + tr.replaceWith(start - 1, end, this.type.create(attributes)); + }, + }), + ]; + }, + }).configure({ + HTMLAttributes: { + class: "mb-6 border-t border-custom-border-300", + }, + }), + SlashCommand(uploadFile, setIsSubmitting), + CodeBlockLowlight.configure({ + lowlight, + }), + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return `Heading ${node.attrs.level}`; + } + if (node.type.name === "image" || node.type.name === "table") { + return ""; + } - return "Press '/' for commands..."; - }, - includeChildren: true, - }), - ]; + return "Press '/' for commands..."; + }, + includeChildren: true, + }), +]; diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx index 7dddc9d98..f8f1f17bb 100644 --- a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/link-selector.tsx @@ -1,7 +1,19 @@ import { Editor } from "@tiptap/core"; import { Check, Trash } from "lucide-react"; -import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react"; -import { cn, isValidHttpUrl, setLinkEditor, unsetLinkEditor, } from "@plane/editor-core"; +import { + Dispatch, + FC, + SetStateAction, + useCallback, + useEffect, + useRef, +} from "react"; +import { + cn, + isValidHttpUrl, + setLinkEditor, + unsetLinkEditor, +} from "@plane/editor-core"; interface LinkSelectorProps { editor: Editor; @@ -9,7 +21,11 @@ interface LinkSelectorProps { setIsOpen: Dispatch>; } -export const LinkSelector: FC = ({ editor, isOpen, setIsOpen }) => { +export const LinkSelector: FC = ({ + editor, + isOpen, + setIsOpen, +}) => { const inputRef = useRef(null); const onLinkSubmit = useCallback(() => { @@ -31,7 +47,7 @@ export const LinkSelector: FC = ({ editor, isOpen, setIsOpen type="button" className={cn( "flex h-full items-center space-x-2 px-3 py-1.5 text-sm font-medium text-custom-text-300 hover:bg-custom-background-100 active:bg-custom-background-100", - { "bg-custom-background-100": isOpen } + { "bg-custom-background-100": isOpen }, )} onClick={() => { setIsOpen(!isOpen); diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx index b8b7ffc58..965e7a42e 100644 --- a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx @@ -1,10 +1,16 @@ -import { BulletListItem, cn, CodeItem, HeadingOneItem, HeadingThreeItem, HeadingTwoItem, NumberedListItem, QuoteItem, TodoListItem } from "@plane/editor-core"; -import { Editor } from "@tiptap/react"; import { - Check, - ChevronDown, - TextIcon, -} from "lucide-react"; + BulletListItem, + cn, + CodeItem, + HeadingOneItem, + HeadingThreeItem, + HeadingTwoItem, + NumberedListItem, + QuoteItem, + TodoListItem, +} from "@plane/editor-core"; +import { Editor } from "@tiptap/react"; +import { Check, ChevronDown, TextIcon } from "lucide-react"; import { Dispatch, FC, SetStateAction } from "react"; import { BubbleMenuItem } from "."; @@ -15,12 +21,17 @@ interface NodeSelectorProps { setIsOpen: Dispatch>; } -export const NodeSelector: FC = ({ editor, isOpen, setIsOpen }) => { +export const NodeSelector: FC = ({ + editor, + isOpen, + setIsOpen, +}) => { const items: BubbleMenuItem[] = [ { name: "Text", icon: TextIcon, - command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(), + command: () => + editor.chain().focus().toggleNode("paragraph", "paragraph").run(), isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && @@ -63,7 +74,10 @@ export const NodeSelector: FC = ({ editor, isOpen, setIsOpen }} className={cn( "flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100", - { "bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name } + { + "bg-custom-primary-100/5 text-custom-text-100": + activeItem.name === item.name, + }, )} >
diff --git a/packages/editor/rich-text-editor/src/ui/read-only/index.tsx b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx index 46905f263..f6ccdddf5 100644 --- a/packages/editor/rich-text-editor/src/ui/read-only/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/read-only/index.tsx @@ -1,6 +1,11 @@ -"use client" -import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core'; -import * as React from 'react'; +"use client"; +import { + EditorContainer, + EditorContentWrapper, + getEditorClassNames, + useReadOnlyEditor, +} from "@plane/editor-core"; +import * as React from "react"; interface IRichTextReadOnlyEditor { value: string; @@ -35,23 +40,31 @@ const RichReadOnlyEditor = ({ mentionHighlights, }); - const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); + const editorClassNames = getEditorClassNames({ + noBorder, + borderOnFocus, + customClassName, + }); if (!editor) return null; return (
- +
-
+ ); }; -const RichReadOnlyEditorWithRef = React.forwardRef((props, ref) => ( - -)); +const RichReadOnlyEditorWithRef = React.forwardRef< + EditorHandle, + IRichTextReadOnlyEditor +>((props, ref) => ); RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef"; -export { RichReadOnlyEditor , RichReadOnlyEditorWithRef }; +export { RichReadOnlyEditor, RichReadOnlyEditorWithRef }; diff --git a/packages/ui/src/avatar/avatar.tsx b/packages/ui/src/avatar/avatar.tsx index 674d82a26..431d693c9 100644 --- a/packages/ui/src/avatar/avatar.tsx +++ b/packages/ui/src/avatar/avatar.tsx @@ -123,7 +123,7 @@ export const Avatar: React.FC = (props) => { size = "md", shape = "circle", src, - className = "" + className = "", } = props; // get size details based on the size prop @@ -157,7 +157,9 @@ export const Avatar: React.FC = (props) => {
( )} ); - } + }, ); Button.displayName = "plane-ui-button"; diff --git a/packages/ui/src/button/helper.tsx b/packages/ui/src/button/helper.tsx index 82489c3e8..48b1fc94a 100644 --- a/packages/ui/src/button/helper.tsx +++ b/packages/ui/src/button/helper.tsx @@ -102,7 +102,7 @@ export const buttonStyling: IButtonStyling = { export const getButtonStyling = ( variant: TButtonVariant, size: TButtonSizes, - disabled: boolean = false + disabled: boolean = false, ): string => { let _variant: string = ``; const currentVariant = buttonStyling[variant]; diff --git a/packages/ui/src/dropdowns/custom-search-select.tsx b/packages/ui/src/dropdowns/custom-search-select.tsx index 3f1e503a8..0fb4c67cf 100644 --- a/packages/ui/src/dropdowns/custom-search-select.tsx +++ b/packages/ui/src/dropdowns/custom-search-select.tsx @@ -35,7 +35,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => { const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState( - null + null, ); const { styles, attributes } = usePopper(referenceElement, popperElement, { @@ -46,7 +46,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => { query === "" ? options : options?.filter((option) => - option.query.toLowerCase().includes(query.toLowerCase()) + option.query.toLowerCase().includes(query.toLowerCase()), ); const comboboxProps: any = { diff --git a/packages/ui/src/dropdowns/custom-select.tsx b/packages/ui/src/dropdowns/custom-select.tsx index dd4d1d786..b62ff2cb3 100644 --- a/packages/ui/src/dropdowns/custom-select.tsx +++ b/packages/ui/src/dropdowns/custom-select.tsx @@ -30,7 +30,7 @@ const CustomSelect = (props: ICustomSelectProps) => { const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState( - null + null, ); const { styles, attributes } = usePopper(referenceElement, popperElement, { diff --git a/packages/ui/src/form-fields/index.ts b/packages/ui/src/form-fields/index.ts index 49f6f1552..9cac73428 100644 --- a/packages/ui/src/form-fields/index.ts +++ b/packages/ui/src/form-fields/index.ts @@ -1,3 +1,3 @@ export * from "./input"; export * from "./textarea"; -export * from "./input-color-picker" \ No newline at end of file +export * from "./input-color-picker"; diff --git a/packages/ui/src/form-fields/textarea.tsx b/packages/ui/src/form-fields/textarea.tsx index e53979edc..8490326b8 100644 --- a/packages/ui/src/form-fields/textarea.tsx +++ b/packages/ui/src/form-fields/textarea.tsx @@ -10,7 +10,7 @@ export interface TextAreaProps // Updates the height of a