fix: project states fixes (#2731)

* fix: project states fixes

* fix: states fixes

* fix: formating all files
This commit is contained in:
sriram veeraghanta 2023-11-08 20:31:46 +05:30 committed by GitHub
parent bd1a850f35
commit 20fb79567f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
156 changed files with 1585 additions and 1758 deletions

View File

@ -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: 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 - 3rd-party libraries being used and their versions
- a use-case that fails - 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. 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 ### Requirements
- Node.js version v16.18.0 - Node.js version v16.18.0
- Python version 3.8+ - Python version 3.8+
- Postgres version v14 - Postgres version v14
- Redis version v6.2.7 - Redis version v6.2.7
### Setup the project ### 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: 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). - 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. - 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 ## 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 ## Ways to contribute
- Try Plane Cloud and the self hosting platform and give feedback - Try Plane Cloud and the self hosting platform and give feedback
- Add new integrations - 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) - 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 - Share your thoughts and suggestions with us
- Help create tutorials and blog posts - Help create tutorials and blog posts
- Request a feature by submitting a proposal - Request a feature by submitting a proposal
- Report a bug - Report a bug
- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations. - **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations.

View File

@ -1,8 +1,10 @@
# Environment Variables # 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 ## {PROJECT_FOLDER}/.env
File is available in the project root folder File is available in the project root folder
``` ```
@ -41,25 +43,37 @@ USE_MINIO=1
# Nginx Configuration # Nginx Configuration
NGINX_PORT=80 NGINX_PORT=80
``` ```
## {PROJECT_FOLDER}/web/.env.example ## {PROJECT_FOLDER}/web/.env.example
``` ```
# Enable/Disable OAUTH - default 0 for selfhosted instance # Enable/Disable OAUTH - default 0 for selfhosted instance
NEXT_PUBLIC_ENABLE_OAUTH=0 NEXT_PUBLIC_ENABLE_OAUTH=0
# Public boards deploy URL # Public boards deploy URL
NEXT_PUBLIC_DEPLOY_URL="http://localhost/spaces" NEXT_PUBLIC_DEPLOY_URL="http://localhost/spaces"
``` ```
## {PROJECT_FOLDER}/spaces/.env.example ## {PROJECT_FOLDER}/spaces/.env.example
``` ```
# Flag to toggle OAuth # Flag to toggle OAuth
NEXT_PUBLIC_ENABLE_OAUTH=0 NEXT_PUBLIC_ENABLE_OAUTH=0
``` ```
## {PROJECT_FOLDER}/apiserver/.env ## {PROJECT_FOLDER}/apiserver/.env
``` ```
# Backend # Backend
# Debug value for api server use it as 0 for production use # Debug value for api server use it as 0 for production use
@ -126,7 +140,9 @@ ENABLE_SIGNUP="1"
# Email Redirection URL # Email Redirection URL
WEB_URL="http://localhost" WEB_URL="http://localhost"
``` ```
## Updates ## Updates
- The environment variable NEXT_PUBLIC_API_BASE_URL has been removed from both the web and space projects. - 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 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. - The plane-worker image will no longer be maintained, as it has been merged with plane-backend.

View File

@ -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. 1. useEditor - A hook that you can use to extend the Plane editor.
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | ------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `extensions` | `Extension[]` | An array of custom extensions you want to add into the editor to extend it's core features | | `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 | | `editorProps` | `EditorProps` | Extend the editor props by passing in a custom props object |
| `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | | `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. |
| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | 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. | | `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. | | `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. | | `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. | | `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". | | `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 | | `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. 2. useReadOnlyEditor - A hook that can be used to extend a Read Only instance of the core editor.
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | -------------- | ------------- | ------------------------------------------------------------------------------------------ |
| `value` | `string` | The initial content of the editor. | | `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 | | `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 | | `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 | | `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. 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 5. Extending with Custom Styles
```ts ```ts
const customEditorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); const customEditorClassNames = getEditorClassNames({
noBorder,
borderOnFocus,
customClassName,
});
``` ```
## Core features ## Core features

View File

@ -3,18 +3,36 @@ import { UploadImage } from "../types/upload-image";
import { startImageUpload } from "../ui/plugins/upload-image"; import { startImageUpload } from "../ui/plugins/upload-image";
export const toggleHeadingOne = (editor: Editor, range?: Range) => { export const toggleHeadingOne = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); if (range)
else editor.chain().focus().toggleHeading({ level: 1 }).run() 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) => { export const toggleHeadingTwo = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); if (range)
else editor.chain().focus().toggleHeading({ level: 2 }).run() 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) => { export const toggleHeadingThree = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); if (range)
else editor.chain().focus().toggleHeading({ level: 3 }).run() 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) => { export const toggleBold = (editor: Editor, range?: Range) => {
@ -37,7 +55,8 @@ export const toggleCode = (editor: Editor, range?: Range) => {
else editor.chain().focus().toggleCode().run(); else editor.chain().focus().toggleCode().run();
}; };
export const toggleOrderedList = (editor: Editor, range?: Range) => { 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(); else editor.chain().focus().toggleOrderedList().run();
}; };
@ -48,7 +67,7 @@ export const toggleBulletList = (editor: Editor, range?: Range) => {
export const toggleTaskList = (editor: Editor, range?: Range) => { export const toggleTaskList = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).toggleTaskList().run(); 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) => { 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) => { export const toggleBlockquote = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(); if (range)
else editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(); 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) => { export const insertTableCommand = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); if (range)
else editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); 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) => { export const unsetLinkEditor = (editor: Editor) => {
@ -74,7 +117,14 @@ export const setLinkEditor = (editor: Editor, url: string) => {
editor.chain().focus().setLink({ href: url }).run(); 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(); if (range) editor.chain().focus().deleteRange(range).run();
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
@ -88,4 +138,3 @@ export const insertImageCommand = (editor: Editor, uploadFile: UploadImage, setI
}; };
input.click(); input.click();
}; };

View File

@ -6,19 +6,24 @@ interface EditorClassNames {
customClassName?: string; customClassName?: string;
} }
export const getEditorClassNames = ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) => cn( export const getEditorClassNames = ({
'relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md', noBorder,
noBorder ? '' : 'border border-custom-border-200', borderOnFocus,
borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0', customClassName,
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[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export const findTableAncestor = ( export const findTableAncestor = (
node: Node | null node: Node | null,
): HTMLTableElement | null => { ): HTMLTableElement | null => {
while (node !== null && node.nodeName !== "TABLE") { while (node !== null && node.nodeName !== "TABLE") {
node = node.parentNode; node = node.parentNode;
@ -27,10 +32,10 @@ export const findTableAncestor = (
}; };
export const getTrimmedHTML = (html: string) => { export const getTrimmedHTML = (html: string) => {
html = html.replace(/^(<p><\/p>)+/, ''); html = html.replace(/^(<p><\/p>)+/, "");
html = html.replace(/(<p><\/p>)+$/, ''); html = html.replace(/(<p><\/p>)+$/, "");
return html; return html;
} };
export const isValidHttpUrl = (string: string): boolean => { export const isValidHttpUrl = (string: string): boolean => {
let url: URL; let url: URL;
@ -42,4 +47,4 @@ export const isValidHttpUrl = (string: string): boolean => {
} }
return url.protocol === "http:" || url.protocol === "https:"; return url.protocol === "http:" || url.protocol === "https:";
} };

View File

@ -1,10 +1,10 @@
export type IMentionSuggestion = { export type IMentionSuggestion = {
id: string; id: string;
type: string; type: string;
avatar: string; avatar: string;
title: string; title: string;
subtitle: string; subtitle: string;
redirect_uri: string; redirect_uri: string;
} };
export type IMentionHighlight = string export type IMentionHighlight = string;

View File

@ -8,10 +8,16 @@ interface EditorContentProps {
children?: ReactNode; children?: ReactNode;
} }
export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = '', children }: EditorContentProps) => ( export const EditorContentWrapper = ({
editor,
editorContentCustomClassNames = "",
children,
}: EditorContentProps) => (
<div className={`contentEditor ${editorContentCustomClassNames}`}> <div className={`contentEditor ${editorContentCustomClassNames}`}>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{(editor?.isActive("image") && editor?.isEditable) && <ImageResizer editor={editor} />} {editor?.isActive("image") && editor?.isEditable && (
<ImageResizer editor={editor} />
)}
{children} {children}
</div> </div>
); );

View File

@ -3,7 +3,9 @@ import Moveable from "react-moveable";
export const ImageResizer = ({ editor }: { editor: Editor }) => { export const ImageResizer = ({ editor }: { editor: Editor }) => {
const updateMediaSize = () => { const updateMediaSize = () => {
const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement; const imageInfo = document.querySelector(
".ProseMirror-selectednode",
) as HTMLImageElement;
if (imageInfo) { if (imageInfo) {
const selection = editor.state.selection; const selection = editor.state.selection;
editor.commands.setImage({ editor.commands.setImage({

View File

@ -1 +1 @@
export { default as default } from "./table-cell" export { default as default } from "./table-cell";

View File

@ -1,7 +1,7 @@
import { mergeAttributes, Node } from "@tiptap/core" import { mergeAttributes, Node } from "@tiptap/core";
export interface TableCellOptions { export interface TableCellOptions {
HTMLAttributes: Record<string, any> HTMLAttributes: Record<string, any>;
} }
export default Node.create<TableCellOptions>({ export default Node.create<TableCellOptions>({
@ -9,8 +9,8 @@ export default Node.create<TableCellOptions>({
addOptions() { addOptions() {
return { return {
HTMLAttributes: {} HTMLAttributes: {},
} };
}, },
content: "paragraph+", content: "paragraph+",
@ -18,24 +18,24 @@ export default Node.create<TableCellOptions>({
addAttributes() { addAttributes() {
return { return {
colspan: { colspan: {
default: 1 default: 1,
}, },
rowspan: { rowspan: {
default: 1 default: 1,
}, },
colwidth: { colwidth: {
default: null, default: null,
parseHTML: (element) => { parseHTML: (element) => {
const colwidth = element.getAttribute("colwidth") const colwidth = element.getAttribute("colwidth");
const value = colwidth ? [parseInt(colwidth, 10)] : null const value = colwidth ? [parseInt(colwidth, 10)] : null;
return value return value;
} },
}, },
background: { background: {
default: "none" default: "none",
} },
} };
}, },
tableRole: "cell", tableRole: "cell",
@ -43,16 +43,16 @@ export default Node.create<TableCellOptions>({
isolating: true, isolating: true,
parseHTML() { parseHTML() {
return [{ tag: "td" }] return [{ tag: "td" }];
}, },
renderHTML({ node, HTMLAttributes }) { renderHTML({ node, HTMLAttributes }) {
return [ return [
"td", "td",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
style: `background-color: ${node.attrs.background}` style: `background-color: ${node.attrs.background}`,
}), }),
0 0,
] ];
} },
}) });

View File

@ -1 +1 @@
export { default as default } from "./table-header" export { default as default } from "./table-header";

View File

@ -1,15 +1,15 @@
import { mergeAttributes, Node } from "@tiptap/core" import { mergeAttributes, Node } from "@tiptap/core";
export interface TableHeaderOptions { export interface TableHeaderOptions {
HTMLAttributes: Record<string, any> HTMLAttributes: Record<string, any>;
} }
export default Node.create<TableHeaderOptions>({ export default Node.create<TableHeaderOptions>({
name: "tableHeader", name: "tableHeader",
addOptions() { addOptions() {
return { return {
HTMLAttributes: {} HTMLAttributes: {},
} };
}, },
content: "paragraph+", content: "paragraph+",
@ -17,24 +17,24 @@ export default Node.create<TableHeaderOptions>({
addAttributes() { addAttributes() {
return { return {
colspan: { colspan: {
default: 1 default: 1,
}, },
rowspan: { rowspan: {
default: 1 default: 1,
}, },
colwidth: { colwidth: {
default: null, default: null,
parseHTML: (element) => { parseHTML: (element) => {
const colwidth = element.getAttribute("colwidth") const colwidth = element.getAttribute("colwidth");
const value = colwidth ? [parseInt(colwidth, 10)] : null const value = colwidth ? [parseInt(colwidth, 10)] : null;
return value return value;
} },
}, },
background: { background: {
default: "rgb(var(--color-primary-100))" default: "rgb(var(--color-primary-100))",
} },
} };
}, },
tableRole: "header_cell", tableRole: "header_cell",
@ -42,16 +42,16 @@ export default Node.create<TableHeaderOptions>({
isolating: true, isolating: true,
parseHTML() { parseHTML() {
return [{ tag: "th" }] return [{ tag: "th" }];
}, },
renderHTML({ node, HTMLAttributes }) { renderHTML({ node, HTMLAttributes }) {
return [ return [
"th", "th",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
style: `background-color: ${node.attrs.background}` style: `background-color: ${node.attrs.background}`,
}), }),
0 0,
] ];
} },
}) });

View File

@ -1 +1 @@
export { default as default } from "./table-row" export { default as default } from "./table-row";

View File

@ -1,31 +1,31 @@
import { mergeAttributes, Node } from "@tiptap/core" import { mergeAttributes, Node } from "@tiptap/core";
export interface TableRowOptions { export interface TableRowOptions {
HTMLAttributes: Record<string, any> HTMLAttributes: Record<string, any>;
} }
export default Node.create<TableRowOptions>({ export default Node.create<TableRowOptions>({
name: "tableRow", name: "tableRow",
addOptions() { addOptions() {
return { return {
HTMLAttributes: {} HTMLAttributes: {},
} };
}, },
content: "(tableCell | tableHeader)*", content: "(tableCell | tableHeader)*",
tableRole: "row", tableRole: "row",
parseHTML() { parseHTML() {
return [{ tag: "tr" }] return [{ tag: "tr" }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"tr", "tr",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0 0,
] ];
} },
}) });

View File

@ -38,7 +38,7 @@ const icons = {
/> />
</svg> </svg>
`, `,
insertBottomTableIcon:`<svg insertBottomTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={24} width={24}
height={24} height={24}

View File

@ -1 +1 @@
export { default as default } from "./table" export { default as default } from "./table";

View File

@ -68,7 +68,12 @@ export function tableControls() {
const { hoveredTable, hoveredCell } = pluginState.values; const { hoveredTable, hoveredCell } = pluginState.values;
const docSize = state.doc.content.size; const docSize = state.doc.content.size;
if (hoveredTable && hoveredCell && hoveredTable.pos < docSize && hoveredCell.pos < docSize) { if (
hoveredTable &&
hoveredCell &&
hoveredTable.pos < docSize &&
hoveredCell.pos < docSize
) {
const decorations = [ const decorations = [
Decoration.node( Decoration.node(
hoveredTable.pos, hoveredTable.pos,

View File

@ -1,298 +1,312 @@
import { TextSelection } from "@tiptap/pm/state" import { TextSelection } from "@tiptap/pm/state";
import { callOrReturn, getExtensionField, mergeAttributes, Node, ParentConfig } from "@tiptap/core"
import { import {
addColumnAfter, callOrReturn,
addColumnBefore, getExtensionField,
addRowAfter, mergeAttributes,
addRowBefore, Node,
CellSelection, ParentConfig,
columnResizing, } from "@tiptap/core";
deleteColumn, import {
deleteRow, addColumnAfter,
deleteTable, addColumnBefore,
fixTables, addRowAfter,
goToNextCell, addRowBefore,
mergeCells, CellSelection,
setCellAttr, columnResizing,
splitCell, deleteColumn,
tableEditing, deleteRow,
toggleHeader, deleteTable,
toggleHeaderCell fixTables,
} from "@tiptap/prosemirror-tables" goToNextCell,
mergeCells,
setCellAttr,
splitCell,
tableEditing,
toggleHeader,
toggleHeaderCell,
} from "@tiptap/prosemirror-tables";
import { tableControls } from "./table-controls" import { tableControls } from "./table-controls";
import { TableView } from "./table-view" import { TableView } from "./table-view";
import { createTable } from "./utilities/create-table" import { createTable } from "./utilities/create-table";
import { deleteTableWhenAllCellsSelected } from "./utilities/delete-table-when-all-cells-selected" import { deleteTableWhenAllCellsSelected } from "./utilities/delete-table-when-all-cells-selected";
export interface TableOptions { export interface TableOptions {
HTMLAttributes: Record<string, any> HTMLAttributes: Record<string, any>;
resizable: boolean resizable: boolean;
handleWidth: number handleWidth: number;
cellMinWidth: number cellMinWidth: number;
lastColumnResizable: boolean lastColumnResizable: boolean;
allowTableNodeSelection: boolean allowTableNodeSelection: boolean;
} }
declare module "@tiptap/core" { declare module "@tiptap/core" {
interface Commands<ReturnType> { interface Commands<ReturnType> {
table: { table: {
insertTable: (options?: { insertTable: (options?: {
rows?: number rows?: number;
cols?: number cols?: number;
withHeaderRow?: boolean withHeaderRow?: boolean;
}) => ReturnType }) => ReturnType;
addColumnBefore: () => ReturnType addColumnBefore: () => ReturnType;
addColumnAfter: () => ReturnType addColumnAfter: () => ReturnType;
deleteColumn: () => ReturnType deleteColumn: () => ReturnType;
addRowBefore: () => ReturnType addRowBefore: () => ReturnType;
addRowAfter: () => ReturnType addRowAfter: () => ReturnType;
deleteRow: () => ReturnType deleteRow: () => ReturnType;
deleteTable: () => ReturnType deleteTable: () => ReturnType;
mergeCells: () => ReturnType mergeCells: () => ReturnType;
splitCell: () => ReturnType splitCell: () => ReturnType;
toggleHeaderColumn: () => ReturnType toggleHeaderColumn: () => ReturnType;
toggleHeaderRow: () => ReturnType toggleHeaderRow: () => ReturnType;
toggleHeaderCell: () => ReturnType toggleHeaderCell: () => ReturnType;
mergeOrSplit: () => ReturnType mergeOrSplit: () => ReturnType;
setCellAttribute: (name: string, value: any) => ReturnType setCellAttribute: (name: string, value: any) => ReturnType;
goToNextCell: () => ReturnType goToNextCell: () => ReturnType;
goToPreviousCell: () => ReturnType goToPreviousCell: () => ReturnType;
fixTables: () => ReturnType fixTables: () => ReturnType;
setCellSelection: (position: { setCellSelection: (position: {
anchorCell: number anchorCell: number;
headCell?: number headCell?: number;
}) => ReturnType }) => ReturnType;
} };
} }
interface NodeConfig<Options, Storage> { interface NodeConfig<Options, Storage> {
tableRole?: tableRole?:
| string | string
| ((this: { | ((this: {
name: string name: string;
options: Options options: Options;
storage: Storage storage: Storage;
parent: ParentConfig<NodeConfig<Options>>["tableRole"] parent: ParentConfig<NodeConfig<Options>>["tableRole"];
}) => string) }) => string);
} }
} }
export default Node.create({ export default Node.create({
name: "table", name: "table",
addOptions() { addOptions() {
return { return {
HTMLAttributes: {}, HTMLAttributes: {},
resizable: true, resizable: true,
handleWidth: 5, handleWidth: 5,
cellMinWidth: 100, cellMinWidth: 100,
lastColumnResizable: true, lastColumnResizable: true,
allowTableNodeSelection: true allowTableNodeSelection: true,
} };
}, },
content: "tableRow+", content: "tableRow+",
tableRole: "table", tableRole: "table",
isolating: true, isolating: true,
group: "block", group: "block",
allowGapCursor: false, allowGapCursor: false,
parseHTML() { parseHTML() {
return [{ tag: "table" }] return [{ tag: "table" }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"table", "table",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
["tbody", 0] ["tbody", 0],
] ];
}, },
addCommands() { addCommands() {
return { return {
insertTable: insertTable:
({ rows = 3, cols = 3, withHeaderRow = true} = {}) => ({ rows = 3, cols = 3, withHeaderRow = true } = {}) =>
({ tr, dispatch, editor }) => { ({ tr, dispatch, editor }) => {
const node = createTable( const node = createTable(editor.schema, rows, cols, withHeaderRow);
editor.schema,
rows,
cols,
withHeaderRow
)
if (dispatch) { if (dispatch) {
const offset = tr.selection.anchor + 1 const offset = tr.selection.anchor + 1;
tr.replaceSelectionWith(node) tr.replaceSelectionWith(node)
.scrollIntoView() .scrollIntoView()
.setSelection( .setSelection(TextSelection.near(tr.doc.resolve(offset)));
TextSelection.near(tr.doc.resolve(offset)) }
)
}
return true return true;
}, },
addColumnBefore: addColumnBefore:
() => () =>
({ state, dispatch }) => addColumnBefore(state, dispatch), ({ state, dispatch }) =>
addColumnAfter: addColumnBefore(state, dispatch),
() => addColumnAfter:
({ state, dispatch }) => addColumnAfter(state, dispatch), () =>
deleteColumn: ({ state, dispatch }) =>
() => addColumnAfter(state, dispatch),
({ state, dispatch }) => deleteColumn(state, dispatch), deleteColumn:
addRowBefore: () =>
() => ({ state, dispatch }) =>
({ state, dispatch }) => addRowBefore(state, dispatch), deleteColumn(state, dispatch),
addRowAfter: addRowBefore:
() => () =>
({ state, dispatch }) => addRowAfter(state, dispatch), ({ state, dispatch }) =>
deleteRow: addRowBefore(state, dispatch),
() => addRowAfter:
({ state, dispatch }) => deleteRow(state, dispatch), () =>
deleteTable: ({ state, dispatch }) =>
() => addRowAfter(state, dispatch),
({ state, dispatch }) => deleteTable(state, dispatch), deleteRow:
mergeCells: () =>
() => ({ state, dispatch }) =>
({ state, dispatch }) => mergeCells(state, dispatch), deleteRow(state, dispatch),
splitCell: deleteTable:
() => () =>
({ state, dispatch }) => splitCell(state, dispatch), ({ state, dispatch }) =>
toggleHeaderColumn: deleteTable(state, dispatch),
() => mergeCells:
({ state, dispatch }) => toggleHeader("column")(state, dispatch), () =>
toggleHeaderRow: ({ state, dispatch }) =>
() => mergeCells(state, dispatch),
({ state, dispatch }) => toggleHeader("row")(state, dispatch), splitCell:
toggleHeaderCell: () =>
() => ({ state, dispatch }) =>
({ state, dispatch }) => toggleHeaderCell(state, dispatch), splitCell(state, dispatch),
mergeOrSplit: toggleHeaderColumn:
() => () =>
({ state, dispatch }) => { ({ state, dispatch }) =>
if (mergeCells(state, dispatch)) { toggleHeader("column")(state, dispatch),
return true 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) return splitCell(state, dispatch);
}, },
setCellAttribute: setCellAttribute:
(name, value) => (name, value) =>
({ state, dispatch }) => setCellAttr(name, value)(state, dispatch), ({ state, dispatch }) =>
goToNextCell: setCellAttr(name, value)(state, dispatch),
() => goToNextCell:
({ state, dispatch }) => goToNextCell(1)(state, dispatch), () =>
goToPreviousCell: ({ state, dispatch }) =>
() => goToNextCell(1)(state, dispatch),
({ state, dispatch }) => goToNextCell(-1)(state, dispatch), goToPreviousCell:
fixTables: () =>
() => ({ state, dispatch }) =>
({ state, dispatch }) => { goToNextCell(-1)(state, dispatch),
if (dispatch) { fixTables:
fixTables(state) () =>
} ({ state, dispatch }) => {
if (dispatch) {
fixTables(state);
}
return true return true;
}, },
setCellSelection: setCellSelection:
(position) => (position) =>
({ tr, dispatch }) => { ({ tr, dispatch }) => {
if (dispatch) { if (dispatch) {
const selection = CellSelection.create( const selection = CellSelection.create(
tr.doc, tr.doc,
position.anchorCell, position.anchorCell,
position.headCell position.headCell,
) );
// @ts-ignore // @ts-ignore
tr.setSelection(selection) tr.setSelection(selection);
} }
return true return true;
} },
} };
}, },
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { return {
Tab: () => { Tab: () => {
if (this.editor.commands.goToNextCell()) { if (this.editor.commands.goToNextCell()) {
return true 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
})
)
} }
return plugins if (!this.editor.can().addRowAfter()) {
}, return false;
extendNodeSchema(extension) {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
} }
return { return this.editor.chain().addRowAfter().goToNextCell().run();
tableRole: callOrReturn( },
getExtensionField(extension, "tableRole", context) "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),
),
};
},
});

View File

@ -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( export function createCell(
cellType: NodeType, cellType: NodeType,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode> cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>,
): ProsemirrorNode | null | undefined { ): ProsemirrorNode | null | undefined {
if (cellContent) { if (cellContent) {
return cellType.createChecked(null, cellContent) return cellType.createChecked(null, cellContent);
} }
return cellType.createAndFill() return cellType.createAndFill();
} }

View File

@ -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 { createCell } from "./create-cell";
import { getTableNodeTypes } from "./get-table-node-types" import { getTableNodeTypes } from "./get-table-node-types";
export function createTable( export function createTable(
schema: Schema, schema: Schema,
rowsCount: number, rowsCount: number,
colsCount: number, colsCount: number,
withHeaderRow: boolean, withHeaderRow: boolean,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode> cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>,
): ProsemirrorNode { ): ProsemirrorNode {
const types = getTableNodeTypes(schema) const types = getTableNodeTypes(schema);
const headerCells: ProsemirrorNode[] = [] const headerCells: ProsemirrorNode[] = [];
const cells: ProsemirrorNode[] = [] const cells: ProsemirrorNode[] = [];
for (let index = 0; index < colsCount; index += 1) { for (let index = 0; index < colsCount; index += 1) {
const cell = createCell(types.cell, cellContent) const cell = createCell(types.cell, cellContent);
if (cell) { if (cell) {
cells.push(cell) cells.push(cell);
}
if (withHeaderRow) {
const headerCell = createCell(types.header_cell, cellContent)
if (headerCell) {
headerCells.push(headerCell)
}
}
} }
const rows: ProsemirrorNode[] = [] if (withHeaderRow) {
const headerCell = createCell(types.header_cell, cellContent);
for (let index = 0; index < rowsCount; index += 1) { if (headerCell) {
rows.push( headerCells.push(headerCell);
types.row.createChecked( }
null,
withHeaderRow && index === 0 ? headerCells : cells
)
)
} }
}
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);
} }

View File

@ -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 = ({ export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({
editor editor,
}) => { }) => {
const { selection } = editor.state const { selection } = editor.state;
if (!isCellSelection(selection)) { if (!isCellSelection(selection)) {
return false 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 if (["tableCell", "tableHeader"].includes(node.type.name)) {
const table = findParentNodeClosestToPos( cellCount += 1;
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
} }
});
editor.commands.deleteTable() const allCellsSelected = cellCount === selection.ranges.length;
return true if (!allCellsSelected) {
} return false;
}
editor.commands.deleteTable();
return true;
};

View File

@ -1,21 +1,21 @@
import { NodeType, Schema } from "prosemirror-model" import { NodeType, Schema } from "prosemirror-model";
export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } { export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } {
if (schema.cached.tableNodeTypes) { if (schema.cached.tableNodeTypes) {
return 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) => { return roles;
const nodeType = schema.nodes[type]
if (nodeType.spec.tableRole) {
roles[nodeType.spec.tableRole] = nodeType
}
})
schema.cached.tableNodeTypes = roles
return roles
} }

View File

@ -1,5 +1,5 @@
import { CellSelection } from "@tiptap/prosemirror-tables" import { CellSelection } from "@tiptap/prosemirror-tables";
export function isCellSelection(value: unknown): value is CellSelection { export function isCellSelection(value: unknown): value is CellSelection {
return value instanceof CellSelection return value instanceof CellSelection;
} }

View File

@ -95,4 +95,3 @@ export const useEditor = ({
return editor; return editor;
}; };

View File

@ -7,7 +7,7 @@ import {
} from "react"; } from "react";
import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions"; import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions";
import { CoreReadOnlyEditorProps } from "../../ui/read-only/props"; 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"; import { IMentionSuggestion } from "../../types/mention-suggestion";
interface CustomReadOnlyEditorProps { interface CustomReadOnlyEditorProps {
@ -19,7 +19,14 @@ interface CustomReadOnlyEditorProps {
mentionSuggestions?: IMentionSuggestion[]; 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({ const editor = useCustomEditor({
editable: false, editable: false,
content: content:
@ -28,7 +35,13 @@ export const useReadOnlyEditor = ({ value, forwardedRef, extensions = [], editor
...CoreReadOnlyEditorProps, ...CoreReadOnlyEditorProps,
...editorProps, ...editorProps,
}, },
extensions: [...CoreReadOnlyEditorExtensions({ mentionSuggestions: mentionSuggestions ?? [], mentionHighlights: mentionHighlights ?? []}), ...extensions], extensions: [
...CoreReadOnlyEditorExtensions({
mentionSuggestions: mentionSuggestions ?? [],
mentionHighlights: mentionHighlights ?? [],
}),
...extensions,
],
}); });
const hasIntiliazedContent = useRef(false); const hasIntiliazedContent = useRef(false);

View File

@ -1,11 +1,11 @@
import { Mention, MentionOptions } from '@tiptap/extension-mention' import { Mention, MentionOptions } from "@tiptap/extension-mention";
import { mergeAttributes } from '@tiptap/core' import { mergeAttributes } from "@tiptap/core";
import { ReactNodeViewRenderer } from '@tiptap/react' import { ReactNodeViewRenderer } from "@tiptap/react";
import mentionNodeView from './mentionNodeView' import mentionNodeView from "./mentionNodeView";
import { IMentionHighlight } from '../../types/mention-suggestion' import { IMentionHighlight } from "../../types/mention-suggestion";
export interface CustomMentionOptions extends MentionOptions { export interface CustomMentionOptions extends MentionOptions {
mentionHighlights: IMentionHighlight[] mentionHighlights: IMentionHighlight[];
readonly?: boolean readonly?: boolean;
} }
export const CustomMention = Mention.extend<CustomMentionOptions>({ export const CustomMention = Mention.extend<CustomMentionOptions>({
@ -21,35 +21,37 @@ export const CustomMention = Mention.extend<CustomMentionOptions>({
default: null, default: null,
}, },
self: { self: {
default: false default: false,
}, },
redirect_uri: { redirect_uri: {
default: "/" default: "/",
} },
} };
}, },
addNodeView() { addNodeView() {
return ReactNodeViewRenderer(mentionNodeView) return ReactNodeViewRenderer(mentionNodeView);
}, },
parseHTML() { parseHTML() {
return [{ return [
tag: 'mention-component', {
getAttrs: (node: string | HTMLElement) => { tag: "mention-component",
if (typeof node === 'string') { getAttrs: (node: string | HTMLElement) => {
return null; if (typeof node === "string") {
} return null;
return { }
id: node.getAttribute('data-mention-id') || '', return {
target: node.getAttribute('data-mention-target') || '', id: node.getAttribute("data-mention-id") || "",
label: node.innerText.slice(1) || '', target: node.getAttribute("data-mention-target") || "",
redirect_uri: node.getAttribute('redirect_uri') label: node.innerText.slice(1) || "",
} redirect_uri: node.getAttribute("redirect_uri"),
};
},
}, },
}] ];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return ['mention-component', mergeAttributes(HTMLAttributes)] return ["mention-component", mergeAttributes(HTMLAttributes)];
}, },
}) });

View File

@ -2,14 +2,21 @@
import suggestion from "./suggestion"; import suggestion from "./suggestion";
import { CustomMention } from "./custom"; import { CustomMention } from "./custom";
import { IMentionHighlight, IMentionSuggestion } from "../../types/mention-suggestion"; import {
IMentionHighlight,
export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighlights: IMentionHighlight[], readonly) => CustomMention.configure({ IMentionSuggestion,
HTMLAttributes: { } from "../../types/mention-suggestion";
'class' : "mention",
},
readonly: readonly,
mentionHighlights: mentionHighlights,
suggestion: suggestion(mentionSuggestions),
})
export const Mentions = (
mentionSuggestions: IMentionSuggestion[],
mentionHighlights: IMentionHighlight[],
readonly,
) =>
CustomMention.configure({
HTMLAttributes: {
class: "mention",
},
readonly: readonly,
mentionHighlights: mentionHighlights,
suggestion: suggestion(mentionSuggestions),
});

View File

@ -1,12 +1,17 @@
import { ReactRenderer } from '@tiptap/react' import { ReactRenderer } from "@tiptap/react";
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import tippy from 'tippy.js' import tippy from "tippy.js";
import MentionList from './MentionList' import MentionList from "./MentionList";
import { IMentionSuggestion } from '../../types/mention-suggestion'; import { IMentionSuggestion } from "../../types/mention-suggestion";
const Suggestion = (suggestions: IMentionSuggestion[]) => ({ 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: () => { render: () => {
let reactRenderer: ReactRenderer | null = null; let reactRenderer: ReactRenderer | null = null;
let popup: any | null = null; let popup: any | null = null;
@ -30,7 +35,7 @@ const Suggestion = (suggestions: IMentionSuggestion[]) => ({
}, },
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => { onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
reactRenderer?.updateProps(props) reactRenderer?.updateProps(props);
popup && popup &&
popup[0].setProps({ popup[0].setProps({
@ -49,11 +54,10 @@ const Suggestion = (suggestions: IMentionSuggestion[]) => ({
}, },
onExit: () => { onExit: () => {
popup?.[0].destroy(); popup?.[0].destroy();
reactRenderer?.destroy() reactRenderer?.destroy();
}, },
} };
}, },
}) });
export default Suggestion; export default Suggestion;

View File

@ -5,7 +5,9 @@ import { UploadImage } from "../types/upload-image";
export function CoreEditorProps( export function CoreEditorProps(
uploadFile: UploadImage, uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
): EditorProps { ): EditorProps {
return { return {
attributes: { 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(); event.preventDefault();
const file = event.clipboardData.files[0]; const file = event.clipboardData.files[0];
const pos = view.state.selection.from; 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(); event.preventDefault();
const file = event.dataTransfer.files[0]; const file = event.dataTransfer.files[0];
const coordinates = view.posAtCoords({ const coordinates = view.posAtCoords({
@ -59,7 +70,13 @@ export function CoreEditorProps(
top: event.clientY, top: event.clientY,
}); });
if (coordinates) { if (coordinates) {
startImageUpload(file, view, coordinates.pos - 1, uploadFile, setIsSubmitting); startImageUpload(
file,
view,
coordinates.pos - 1,
uploadFile,
setIsSubmitting,
);
} }
return true; return true;
} }

View File

@ -18,9 +18,10 @@ import { isValidHttpUrl } from "../../lib/utils";
import { Mentions } from "../mentions"; import { Mentions } from "../mentions";
import { IMentionSuggestion } from "../../types/mention-suggestion"; import { IMentionSuggestion } from "../../types/mention-suggestion";
export const CoreReadOnlyEditorExtensions = ( export const CoreReadOnlyEditorExtensions = (mentionConfig: {
mentionConfig: { mentionSuggestions: IMentionSuggestion[], mentionHighlights: string[] }, mentionSuggestions: IMentionSuggestion[];
) => [ mentionHighlights: string[];
}) => [
StarterKit.configure({ StarterKit.configure({
bulletList: { bulletList: {
HTMLAttributes: { HTMLAttributes: {
@ -57,41 +58,45 @@ export const CoreReadOnlyEditorExtensions = (
}, },
gapcursor: false, gapcursor: false,
}), }),
Gapcursor, Gapcursor,
TiptapLink.configure({ TiptapLink.configure({
protocols: ["http", "https"], protocols: ["http", "https"],
validate: (url) => isValidHttpUrl(url), validate: (url) => isValidHttpUrl(url),
HTMLAttributes: { HTMLAttributes: {
class: class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
}, },
}), }),
ReadOnlyImageExtension.configure({ ReadOnlyImageExtension.configure({
HTMLAttributes: { HTMLAttributes: {
class: "rounded-lg border border-custom-border-300", class: "rounded-lg border border-custom-border-300",
}, },
}), }),
TiptapUnderline, TiptapUnderline,
TextStyle, TextStyle,
Color, Color,
TaskList.configure({ TaskList.configure({
HTMLAttributes: { HTMLAttributes: {
class: "not-prose pl-2", class: "not-prose pl-2",
}, },
}), }),
TaskItem.configure({ TaskItem.configure({
HTMLAttributes: { HTMLAttributes: {
class: "flex items-start my-4", class: "flex items-start my-4",
}, },
nested: true, nested: true,
}), }),
Markdown.configure({ Markdown.configure({
html: true, html: true,
transformCopiedText: true, transformCopiedText: true,
}), }),
Table, Table,
TableHeader, TableHeader,
TableCell, TableCell,
TableRow, TableRow,
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, true), Mentions(
]; mentionConfig.mentionSuggestions,
mentionConfig.mentionHighlights,
true,
),
];

View File

@ -1,7 +1,6 @@
import { EditorProps } from "@tiptap/pm/view"; import { EditorProps } from "@tiptap/pm/view";
export const CoreReadOnlyEditorProps: EditorProps = export const CoreReadOnlyEditorProps: EditorProps = {
{
attributes: { attributes: {
class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`, class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
}, },

View File

@ -10,25 +10,25 @@ The `@plane/lite-text-editor` package extends from the `editor-core` package, in
`LiteTextEditor` & `LiteTextEditorWithRef` `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` `LiteReadOnlyEditor` &`LiteReadOnlyEditorWithRef`
## LiteTextEditor ## LiteTextEditor
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | | `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. |
| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | 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. | | `value` | `html string` | The initial content of the editor. |
| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press | | `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. | | `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. | | `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. | | `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". | | `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. | | `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. | | `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. | | `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. | | `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage ### Usage
@ -36,62 +36,62 @@ The `@plane/lite-text-editor` package extends from the `editor-core` package, in
```tsx ```tsx
<LiteTextEditor <LiteTextEditor
onEnterKeyPress={handleSubmit(handleCommentUpdate)} onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)} uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage} deleteFile={fileService.deleteImage}
value={value} value={value}
debouncedUpdatesEnabled={false} debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm" customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => { onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html); onChange(comment_html);
}} }}
/> />
``` ```
2. Example of how to use the `LiteTextEditorWithRef` component 2. Example of how to use the `LiteTextEditorWithRef` component
```tsx ```tsx
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
// can use it to set the editor's value // can use it to set the editor's value
editorRef.current?.setEditorValue(`${watch("description_html")}`); editorRef.current?.setEditorValue(`${watch("description_html")}`);
// can use it to clear the editor // can use it to clear the editor
editorRef?.current?.clearEditor(); editorRef?.current?.clearEditor();
return ( return (
<LiteTextEditorWithRef <LiteTextEditorWithRef
onEnterKeyPress={handleSubmit(handleCommentUpdate)} onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)} uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage} deleteFile={fileService.deleteImage}
ref={editorRef} ref={editorRef}
value={value} value={value}
debouncedUpdatesEnabled={false} debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm" customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => { onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html); onChange(comment_html);
}} }}
/> />
) );
``` ```
## LiteReadOnlyEditor ## LiteReadOnlyEditor
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | ------------------------------- | ------------- | --------------------------------------------------------------------- |
| `value` | `html string` | The initial content of the editor. | | `value` | `html string` | The initial content of the editor. |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. | | `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. | | `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. | | `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. | | `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage ### Usage
Here is an example of how to use the `RichReadOnlyEditor` component Here is an example of how to use the `RichReadOnlyEditor` component
```tsx ```tsx
<LiteReadOnlyEditor <LiteReadOnlyEditor
value={comment.comment_html} value={comment.comment_html}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100" customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
/> />
``` ```

View File

@ -1,3 +1,3 @@
export { LiteTextEditor, LiteTextEditorWithRef } from "./ui"; export { LiteTextEditor, LiteTextEditorWithRef } from "./ui";
export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "./ui/read-only"; export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "./ui/read-only";
export type { IMentionSuggestion, IMentionHighlight } from "./ui" export type { IMentionSuggestion, IMentionHighlight } from "./ui";

View File

@ -31,7 +31,7 @@ interface ILiteTextEditor {
editorContentCustomClassNames?: string; editorContentCustomClassNames?: string;
onChange?: (json: any, html: string) => void; onChange?: (json: any, html: string) => void;
setIsSubmitting?: ( setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved" isSubmitting: "submitting" | "submitted" | "saved",
) => void; ) => void;
setShouldShowAlert?: (showAlert: boolean) => void; setShouldShowAlert?: (showAlert: boolean) => void;
forwardedRef?: any; forwardedRef?: any;
@ -129,7 +129,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
}; };
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>( const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>(
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} /> (props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />,
); );
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef"; LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";

View File

@ -6,8 +6,9 @@ type Props = {
}; };
export const Icon: React.FC<Props> = ({ iconName, className = "" }) => ( export const Icon: React.FC<Props> = ({ iconName, className = "" }) => (
<span className={`material-symbols-rounded text-sm leading-5 font-light ${className}`}> <span
className={`material-symbols-rounded text-sm leading-5 font-light ${className}`}
>
{iconName} {iconName}
</span> </span>
); );

View File

@ -10,24 +10,24 @@ The `@plane/rich-text-editor` package extends from the `editor-core` package, in
`RichTextEditor` & `RichTextEditorWithRef` `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` `RichReadOnlyEditor` &`RichReadOnlyEditorWithRef`
## RichTextEditor ## RichTextEditor
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. | | `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. |
| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. | | `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | 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. | | `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. | | `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. | | `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. | | `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". | | `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. | | `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. | | `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. | | `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. | | `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage ### 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 2. Example of how to use the `RichTextEditorWithRef` component
```tsx ```tsx
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
// can use it to set the editor's value // can use it to set the editor's value
editorRef.current?.setEditorValue(`${watch("description_html")}`); editorRef.current?.setEditorValue(`${watch("description_html")}`);
// can use it to clear the editor // can use it to clear the editor
editorRef?.current?.clearEditor(); editorRef?.current?.clearEditor();
return (<RichTextEditorWithRef return (
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)} <RichTextEditorWithRef
deleteFile={fileService.deleteImage} uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
ref={editorRef} deleteFile={fileService.deleteImage}
debouncedUpdatesEnabled={false} ref={editorRef}
value={value} debouncedUpdatesEnabled={false}
customClassName="min-h-[150px]" value={value}
onChange={(description: Object, description_html: string) => { customClassName="min-h-[150px]"
onChange(description_html); onChange={(description: Object, description_html: string) => {
// custom stuff you want to do onChange(description_html);
} } />) // custom stuff you want to do
}}
/>
);
``` ```
## RichReadOnlyEditor ## RichReadOnlyEditor
| Prop | Type | Description | | Prop | Type | Description |
| --- | --- | --- | | ------------------------------- | ------------- | --------------------------------------------------------------------- |
| `value` | `html string` | The initial content of the editor. | | `value` | `html string` | The initial content of the editor. |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. | | `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. | | `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. | | `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. | | `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage ### Usage
Here is an example of how to use the `RichReadOnlyEditor` component Here is an example of how to use the `RichReadOnlyEditor` component
```tsx ```tsx
<RichReadOnlyEditor <RichReadOnlyEditor
value={issueDetails.description_html} value={issueDetails.description_html}
customClassName="p-3 min-h-[50px] shadow-sm" /> customClassName="p-3 min-h-[50px] shadow-sm"
/>
``` ```

View File

@ -2,4 +2,4 @@ import "./styles/github-dark.css";
export { RichTextEditor, RichTextEditorWithRef } from "./ui"; export { RichTextEditor, RichTextEditorWithRef } from "./ui";
export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "./ui/read-only"; export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "./ui/read-only";
export type { IMentionSuggestion, IMentionHighlight } from "./ui" export type { IMentionSuggestion, IMentionHighlight } from "./ui";

View File

@ -1,7 +1,7 @@
import HorizontalRule from "@tiptap/extension-horizontal-rule"; import HorizontalRule from "@tiptap/extension-horizontal-rule";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
import { common, createLowlight } from 'lowlight' import { common, createLowlight } from "lowlight";
import { InputRule } from "@tiptap/core"; import { InputRule } from "@tiptap/core";
import ts from "highlight.js/lib/languages/typescript"; 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 SlashCommand from "./slash-command";
import { UploadImage } from "../"; import { UploadImage } from "../";
const lowlight = createLowlight(common) const lowlight = createLowlight(common);
lowlight.register("ts", ts); lowlight.register("ts", ts);
export const RichTextEditorExtensions = ( export const RichTextEditorExtensions = (
uploadFile: UploadImage, uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
) => [ ) => [
HorizontalRule.extend({ HorizontalRule.extend({
addInputRules() { addInputRules() {
return [ return [
new InputRule({ new InputRule({
find: /^(?:---|—-|___\s|\*\*\*\s)$/, find: /^(?:---|—-|___\s|\*\*\*\s)$/,
handler: ({ state, range, commands }) => { handler: ({ state, range, commands }) => {
commands.splitBlock(); commands.splitBlock();
const attributes = {}; const attributes = {};
const { tr } = state; const { tr } = state;
const start = range.from; const start = range.from;
const end = range.to; const end = range.to;
// @ts-ignore // @ts-ignore
tr.replaceWith(start - 1, end, this.type.create(attributes)); tr.replaceWith(start - 1, end, this.type.create(attributes));
}, },
}), }),
]; ];
}, },
}).configure({ }).configure({
HTMLAttributes: { HTMLAttributes: {
class: "mb-6 border-t border-custom-border-300", class: "mb-6 border-t border-custom-border-300",
}, },
}), }),
SlashCommand(uploadFile, setIsSubmitting), SlashCommand(uploadFile, setIsSubmitting),
CodeBlockLowlight.configure({ CodeBlockLowlight.configure({
lowlight, lowlight,
}), }),
Placeholder.configure({ Placeholder.configure({
placeholder: ({ node }) => { placeholder: ({ node }) => {
if (node.type.name === "heading") { if (node.type.name === "heading") {
return `Heading ${node.attrs.level}`; return `Heading ${node.attrs.level}`;
} }
if (node.type.name === "image" || node.type.name === "table") { if (node.type.name === "image" || node.type.name === "table") {
return ""; return "";
} }
return "Press '/' for commands..."; return "Press '/' for commands...";
}, },
includeChildren: true, includeChildren: true,
}), }),
]; ];

View File

@ -1,7 +1,19 @@
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import { Check, Trash } from "lucide-react"; import { Check, Trash } from "lucide-react";
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react"; import {
import { cn, isValidHttpUrl, setLinkEditor, unsetLinkEditor, } from "@plane/editor-core"; Dispatch,
FC,
SetStateAction,
useCallback,
useEffect,
useRef,
} from "react";
import {
cn,
isValidHttpUrl,
setLinkEditor,
unsetLinkEditor,
} from "@plane/editor-core";
interface LinkSelectorProps { interface LinkSelectorProps {
editor: Editor; editor: Editor;
@ -9,7 +21,11 @@ interface LinkSelectorProps {
setIsOpen: Dispatch<SetStateAction<boolean>>; setIsOpen: Dispatch<SetStateAction<boolean>>;
} }
export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen }) => { export const LinkSelector: FC<LinkSelectorProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const onLinkSubmit = useCallback(() => { const onLinkSubmit = useCallback(() => {
@ -31,7 +47,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
type="button" type="button"
className={cn( 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", "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={() => { onClick={() => {
setIsOpen(!isOpen); setIsOpen(!isOpen);

View File

@ -1,10 +1,16 @@
import { BulletListItem, cn, CodeItem, HeadingOneItem, HeadingThreeItem, HeadingTwoItem, NumberedListItem, QuoteItem, TodoListItem } from "@plane/editor-core";
import { Editor } from "@tiptap/react";
import { import {
Check, BulletListItem,
ChevronDown, cn,
TextIcon, CodeItem,
} from "lucide-react"; 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 { Dispatch, FC, SetStateAction } from "react";
import { BubbleMenuItem } from "."; import { BubbleMenuItem } from ".";
@ -15,12 +21,17 @@ interface NodeSelectorProps {
setIsOpen: Dispatch<SetStateAction<boolean>>; setIsOpen: Dispatch<SetStateAction<boolean>>;
} }
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => { export const NodeSelector: FC<NodeSelectorProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
const items: BubbleMenuItem[] = [ const items: BubbleMenuItem[] = [
{ {
name: "Text", name: "Text",
icon: TextIcon, icon: TextIcon,
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(), command: () =>
editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
isActive: () => isActive: () =>
editor.isActive("paragraph") && editor.isActive("paragraph") &&
!editor.isActive("bulletList") && !editor.isActive("bulletList") &&
@ -63,7 +74,10 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
}} }}
className={cn( 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", "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,
},
)} )}
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">

View File

@ -1,6 +1,11 @@
"use client" "use client";
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core'; import {
import * as React from 'react'; EditorContainer,
EditorContentWrapper,
getEditorClassNames,
useReadOnlyEditor,
} from "@plane/editor-core";
import * as React from "react";
interface IRichTextReadOnlyEditor { interface IRichTextReadOnlyEditor {
value: string; value: string;
@ -35,23 +40,31 @@ const RichReadOnlyEditor = ({
mentionHighlights, mentionHighlights,
}); });
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName }); const editorClassNames = getEditorClassNames({
noBorder,
borderOnFocus,
customClassName,
});
if (!editor) return null; if (!editor) return null;
return ( return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}> <EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col"> <div className="flex flex-col">
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} /> <EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
</div> </div>
</EditorContainer > </EditorContainer>
); );
}; };
const RichReadOnlyEditorWithRef = React.forwardRef<EditorHandle, IRichTextReadOnlyEditor>((props, ref) => ( const RichReadOnlyEditorWithRef = React.forwardRef<
<RichReadOnlyEditor {...props} forwardedRef={ref} /> EditorHandle,
)); IRichTextReadOnlyEditor
>((props, ref) => <RichReadOnlyEditor {...props} forwardedRef={ref} />);
RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef"; RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";
export { RichReadOnlyEditor , RichReadOnlyEditorWithRef }; export { RichReadOnlyEditor, RichReadOnlyEditorWithRef };

View File

@ -123,7 +123,7 @@ export const Avatar: React.FC<Props> = (props) => {
size = "md", size = "md",
shape = "circle", shape = "circle",
src, src,
className = "" className = "",
} = props; } = props;
// get size details based on the size prop // get size details based on the size prop
@ -157,7 +157,9 @@ export const Avatar: React.FC<Props> = (props) => {
<div <div
className={`${ className={`${
sizeInfo.fontSize sizeInfo.fontSize
} grid place-items-center h-full w-full ${getBorderRadius(shape)} ${className}`} } grid place-items-center h-full w-full ${getBorderRadius(
shape,
)} ${className}`}
style={{ style={{
backgroundColor: backgroundColor:
fallbackBackgroundColor ?? "rgba(var(--color-primary-500))", fallbackBackgroundColor ?? "rgba(var(--color-primary-500))",

View File

@ -58,7 +58,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
)} )}
</button> </button>
); );
} },
); );
Button.displayName = "plane-ui-button"; Button.displayName = "plane-ui-button";

View File

@ -102,7 +102,7 @@ export const buttonStyling: IButtonStyling = {
export const getButtonStyling = ( export const getButtonStyling = (
variant: TButtonVariant, variant: TButtonVariant,
size: TButtonSizes, size: TButtonSizes,
disabled: boolean = false disabled: boolean = false,
): string => { ): string => {
let _variant: string = ``; let _variant: string = ``;
const currentVariant = buttonStyling[variant]; const currentVariant = buttonStyling[variant];

View File

@ -35,7 +35,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
const [referenceElement, setReferenceElement] = const [referenceElement, setReferenceElement] =
useState<HTMLButtonElement | null>(null); useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>( const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null null,
); );
const { styles, attributes } = usePopper(referenceElement, popperElement, { const { styles, attributes } = usePopper(referenceElement, popperElement, {
@ -46,7 +46,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
query === "" query === ""
? options ? options
: options?.filter((option) => : options?.filter((option) =>
option.query.toLowerCase().includes(query.toLowerCase()) option.query.toLowerCase().includes(query.toLowerCase()),
); );
const comboboxProps: any = { const comboboxProps: any = {

View File

@ -30,7 +30,7 @@ const CustomSelect = (props: ICustomSelectProps) => {
const [referenceElement, setReferenceElement] = const [referenceElement, setReferenceElement] =
useState<HTMLButtonElement | null>(null); useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>( const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null null,
); );
const { styles, attributes } = usePopper(referenceElement, popperElement, { const { styles, attributes } = usePopper(referenceElement, popperElement, {

View File

@ -1,3 +1,3 @@
export * from "./input"; export * from "./input";
export * from "./textarea"; export * from "./textarea";
export * from "./input-color-picker" export * from "./input-color-picker";

View File

@ -10,7 +10,7 @@ export interface TextAreaProps
// Updates the height of a <textarea> when the value changes. // Updates the height of a <textarea> when the value changes.
const useAutoSizeTextArea = ( const useAutoSizeTextArea = (
textAreaRef: HTMLTextAreaElement | null, textAreaRef: HTMLTextAreaElement | null,
value: any value: any,
) => { ) => {
React.useEffect(() => { React.useEffect(() => {
if (textAreaRef) { if (textAreaRef) {
@ -63,7 +63,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
{...rest} {...rest}
/> />
); );
} },
); );
export { TextArea }; export { TextArea };

View File

@ -9,7 +9,7 @@ interface ICircularProgressIndicator {
} }
export const CircularProgressIndicator: React.FC<ICircularProgressIndicator> = ( export const CircularProgressIndicator: React.FC<ICircularProgressIndicator> = (
props props,
) => { ) => {
const { size = 40, percentage = 25, strokeWidth = 6, children } = props; const { size = 40, percentage = 25, strokeWidth = 6, children } = props;

View File

@ -9,7 +9,6 @@ type Props = {
}; };
export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => { export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => {
const mentionConfig = useEditorSuggestions(); const mentionConfig = useEditorSuggestions();
return ( return (
@ -20,15 +19,19 @@ export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => {
<h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4> <h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
{issueDetails.description_html !== "" && issueDetails.description_html !== "<p></p>" && ( {issueDetails.description_html !== "" && issueDetails.description_html !== "<p></p>" && (
<RichReadOnlyEditor <RichReadOnlyEditor
value={!issueDetails.description_html || value={
!issueDetails.description_html ||
issueDetails.description_html === "" || issueDetails.description_html === "" ||
(typeof issueDetails.description_html === "object" && (typeof issueDetails.description_html === "object" &&
Object.keys(issueDetails.description_html).length === 0) Object.keys(issueDetails.description_html).length === 0)
? "<p></p>" ? "<p></p>"
: issueDetails.description_html} : issueDetails.description_html
customClassName="p-3 min-h-[50px] shadow-sm" mentionHighlights={mentionConfig.mentionHighlights} /> }
customClassName="p-3 min-h-[50px] shadow-sm"
mentionHighlights={mentionConfig.mentionHighlights}
/>
)} )}
<IssueReactions /> <IssueReactions />
</div> </div>
) );
}; };

View File

@ -3,43 +3,41 @@ import { RootStore } from "./root";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
export interface IMentionsStore { export interface IMentionsStore {
// mentionSuggestions: IMentionSuggestion[]; // mentionSuggestions: IMentionSuggestion[];
mentionHighlights: IMentionHighlight[]; mentionHighlights: IMentionHighlight[];
} }
export class MentionsStore implements IMentionsStore{ export class MentionsStore implements IMentionsStore {
// root store
rootStore;
// root store constructor(_rootStore: RootStore) {
rootStore; // rootStore
this.rootStore = _rootStore;
constructor(_rootStore: RootStore ){ makeObservable(this, {
mentionHighlights: computed,
// mentionSuggestions: computed
});
}
// rootStore // get mentionSuggestions() {
this.rootStore = _rootStore; // const projectMembers = this.rootStore.project.project.
makeObservable(this, { // const suggestions = projectMembers === null ? [] : projectMembers.map((member) => ({
mentionHighlights: computed, // id: member.member.id,
// mentionSuggestions: computed // type: "User",
}) // title: member.member.display_name,
} // subtitle: member.member.email ?? "",
// avatar: member.member.avatar,
// redirect_uri: `/${member.workspace.slug}/profile/${member.member.id}`,
// }))
// get mentionSuggestions() { // return suggestions
// const projectMembers = this.rootStore.project.project. // }
// const suggestions = projectMembers === null ? [] : projectMembers.map((member) => ({ get mentionHighlights() {
// id: member.member.id, const user = this.rootStore.user.currentUser;
// type: "User", return user ? [user.id] : [];
// title: member.member.display_name, }
// subtitle: member.member.email ?? "", }
// avatar: member.member.avatar,
// redirect_uri: `/${member.workspace.slug}/profile/${member.member.id}`,
// }))
// return suggestions
// }
get mentionHighlights() {
const user = this.rootStore.user.currentUser;
return user ? [user.id] : []
}
}

View File

@ -32,8 +32,7 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
}, [code, gitCode, handleSignIn]); }, [code, gitCode, handleSignIn]);
useEffect(() => { useEffect(() => {
const origin = const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
setLoginCallBackURL(`${origin}/` as any); setLoginCallBackURL(`${origin}/` as any);
}, []); }, []);

View File

@ -49,10 +49,7 @@ export const CustomTooltip: React.FC<Props> = ({ datum, analytics, params }) =>
: "" : ""
}`} }`}
> >
{params.segment === "assignees__id" {params.segment === "assignees__id" ? renderAssigneeName(tooltipValue.toString()) : tooltipValue}:
? renderAssigneeName(tooltipValue.toString())
: tooltipValue}
:
</span> </span>
<span>{datum.value}</span> <span>{datum.value}</span>
</div> </div>

View File

@ -22,9 +22,7 @@ export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
keys={["count"]} keys={["count"]}
height="250px" height="250px"
colors={() => `#f97316`} colors={() => `#f97316`}
customYAxisTickValues={defaultAnalytics.pending_issue_user.map((d) => customYAxisTickValues={defaultAnalytics.pending_issue_user.map((d) => (d.count > 0 ? d.count : 50))}
d.count > 0 ? d.count : 50
)}
tooltip={(datum) => { tooltip={(datum) => {
const assignee = defaultAnalytics.pending_issue_user.find( const assignee = defaultAnalytics.pending_issue_user.find(
(a) => a.assignees__id === `${datum.indexValue}` (a) => a.assignees__id === `${datum.indexValue}`

View File

@ -31,9 +31,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
alt="ProjectSettingImg" alt="ProjectSettingImg"
/> />
</div> </div>
<h1 className="text-xl font-medium text-custom-text-100"> <h1 className="text-xl font-medium text-custom-text-100">Oops! You are not authorized to view this page</h1>
Oops! You are not authorized to view this page
</h1>
<div className="w-full max-w-md text-base text-custom-text-200"> <div className="w-full max-w-md text-base text-custom-text-200">
{user ? ( {user ? (

View File

@ -7,8 +7,6 @@ import { SelectMonthModal } from "components/automation";
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui"; import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
// icons // icons
import { ArchiveX } from "lucide-react"; import { ArchiveX } from "lucide-react";
// helpers
import { getStatesList } from "helpers/state.helper";
// types // types
import { IProject } from "types"; import { IProject } from "types";
// fetch keys // fetch keys
@ -23,12 +21,12 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
// states // states
const [monthModal, setmonthModal] = useState(false); const [monthModal, setmonthModal] = useState(false);
const { user: userStore, project: projectStore } = useMobxStore(); const { user: userStore, project: projectStore, projectState: projectStateStore } = useMobxStore();
const userRole = userStore.currentProjectRole; const userRole = userStore.currentProjectRole;
const projectDetails = projectStore.currentProjectDetails; const projectDetails = projectStore.currentProjectDetails;
const stateGroups = projectStore.projectStatesByGroups ?? undefined; // const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
const states = getStatesList(stateGroups); const states = projectStateStore.projectStates;
const options = states const options = states
?.filter((state) => state.group === "cancelled") ?.filter((state) => state.group === "cancelled")
@ -45,7 +43,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
const multipleOptions = (options ?? []).length > 1; const multipleOptions = (options ?? []).length > 1;
const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null; const defaultState = states?.find((s) => s.group === "cancelled")?.id || null;
const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState); const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState);
const currentDefaultState = states?.find((s) => s.id === defaultState); const currentDefaultState = states?.find((s) => s.id === defaultState);

View File

@ -1,9 +1,6 @@
import React, { Dispatch, SetStateAction, useCallback } from "react"; import React, { Dispatch, SetStateAction, useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// cmdk // cmdk
import { Command } from "cmdk"; import { Command } from "cmdk";
// services // services
@ -13,8 +10,6 @@ import { ProjectStateService } from "services/project";
import { Spinner, StateGroupIcon } from "@plane/ui"; import { Spinner, StateGroupIcon } from "@plane/ui";
// icons // icons
import { Check } from "lucide-react"; import { Check } from "lucide-react";
// helpers
import { getStatesList } from "helpers/state.helper";
// types // types
import { IUser, IIssue } from "types"; import { IUser, IIssue } from "types";
// fetch keys // fetch keys
@ -34,11 +29,10 @@ export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue, use
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
const { data: stateGroups, mutate: mutateIssueDetails } = useSWR( const { data: states, mutate: mutateStates } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
); );
const states = getStatesList(stateGroups);
const submitChanges = useCallback( const submitChanges = useCallback(
async (formData: Partial<IIssue>) => { async (formData: Partial<IIssue>) => {
@ -60,14 +54,14 @@ export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue, use
await issueService await issueService
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user)
.then(() => { .then(() => {
mutateIssueDetails(); mutateStates();
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}) })
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
}); });
}, },
[workspaceSlug, issueId, projectId, mutateIssueDetails, user] [workspaceSlug, issueId, projectId, mutateStates, user]
); );
const handleIssueState = (stateId: string) => { const handleIssueState = (stateId: string) => {

View File

@ -57,8 +57,7 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
const interval = Math.ceil(totalDates / maxDates); const interval = Math.ceil(totalDates / maxDates);
const limitedDates = []; const limitedDates = [];
for (let i = 0; i < totalDates; i += interval) for (let i = 0; i < totalDates; i += interval) limitedDates.push(renderShortNumericDateFormat(dates[i]));
limitedDates.push(renderShortNumericDateFormat(dates[i]));
if (!limitedDates.includes(renderShortNumericDateFormat(dates[totalDates - 1]))) if (!limitedDates.includes(renderShortNumericDateFormat(dates[totalDates - 1])))
limitedDates.push(renderShortNumericDateFormat(dates[totalDates - 1])); limitedDates.push(renderShortNumericDateFormat(dates[totalDates - 1]));

View File

@ -231,7 +231,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
</button> </button>
)} )}
<CustomMenu width="auto" ellipsis > <CustomMenu width="auto" ellipsis>
{!isCompleted && ( {!isCompleted && (
<> <>
<CustomMenu.MenuItem onClick={handleEditCycle}> <CustomMenu.MenuItem onClick={handleEditCycle}>

View File

@ -34,16 +34,8 @@ export const MonthChartView: FC<any> = () => {
style={{ width: `${currentViewData?.data.width}px` }} style={{ width: `${currentViewData?.data.width}px` }}
> >
<div className="text-xs space-x-1"> <div className="text-xs space-x-1">
<span className="text-custom-text-200"> <span className="text-custom-text-200">{monthDay.dayData.shortTitle[0]}</span>{" "}
{monthDay.dayData.shortTitle[0]} <span className={monthDay.today ? "bg-custom-primary-100 text-white px-1 rounded-full" : ""}>
</span>{" "}
<span
className={
monthDay.today
? "bg-custom-primary-100 text-white px-1 rounded-full"
: ""
}
>
{monthDay.day} {monthDay.day}
</span> </span>
</div> </div>
@ -63,9 +55,7 @@ export const MonthChartView: FC<any> = () => {
> >
<div <div
className={`relative h-full w-full flex-1 flex justify-center ${ className={`relative h-full w-full flex-1 flex justify-center ${
["sat", "sun"].includes(monthDay?.dayData?.shortTitle || "") ["sat", "sun"].includes(monthDay?.dayData?.shortTitle || "") ? `bg-custom-background-90` : ``
? `bg-custom-background-90`
: ``
}`} }`}
> >
{/* {monthDay?.today && ( {/* {monthDay?.today && (

View File

@ -27,8 +27,7 @@ export const months: WeekMonthDataType[] = [
{ key: 11, shortTitle: "dec", title: "december" }, { key: 11, shortTitle: "dec", title: "december" },
]; ];
export const charCapitalize = (word: string) => export const charCapitalize = (word: string) => `${word.charAt(0).toUpperCase()}${word.substring(1)}`;
`${word.charAt(0).toUpperCase()}${word.substring(1)}`;
export const bindZero = (value: number) => (value > 9 ? `${value}` : `0${value}`); export const bindZero = (value: number) => (value > 9 ? `${value}` : `0${value}`);
@ -50,9 +49,7 @@ export const datePreview = (date: Date, includeTime: boolean = false) => {
month = months[month as number] as WeekMonthDataType; month = months[month as number] as WeekMonthDataType;
const year = date.getFullYear(); const year = date.getFullYear();
return `${charCapitalize(month?.shortTitle)} ${day}, ${year}${ return `${charCapitalize(month?.shortTitle)} ${day}, ${year}${includeTime ? `, ${timePreview(date)}` : ``}`;
includeTime ? `, ${timePreview(date)}` : ``
}`;
}; };
// context data // context data
@ -137,8 +134,6 @@ export const allViewsWithData: ChartDataType[] = [
]; ];
export const currentViewDataWithView = (view: string = "month") => { export const currentViewDataWithView = (view: string = "month") => {
const currentView: ChartDataType | undefined = allViewsWithData.find( const currentView: ChartDataType | undefined = allViewsWithData.find((_viewData) => _viewData.key === view);
(_viewData) => _viewData.key === view
);
return currentView; return currentView;
}; };

View File

@ -3,12 +3,7 @@ import { ChartDataType } from "../types";
// data // data
import { weeks, months } from "../data"; import { weeks, months } from "../data";
// helpers // helpers
import { import { generateDate, getWeekNumberByDate, getNumberOfDaysInMonth, getDatesBetweenTwoDates } from "./helpers";
generateDate,
getWeekNumberByDate,
getNumberOfDaysInMonth,
getDatesBetweenTwoDates,
} from "./helpers";
type GetAllDaysInMonthInMonthViewType = { type GetAllDaysInMonthInMonthViewType = {
date: any; date: any;
@ -34,9 +29,7 @@ const getAllDaysInMonthInMonthView = (month: number, year: number) => {
title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`, title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`,
active: false, active: false,
today: today:
currentDate.getFullYear() === year && currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === _day + 1
currentDate.getMonth() === month &&
currentDate.getDate() === _day + 1
? true ? true
: false, : false,
}); });
@ -72,16 +65,8 @@ export const generateBiWeekChart = (monthPayload: ChartDataType, side: null | "l
if (side === null) { if (side === null) {
const currentDate = renderState.data.currentDate; const currentDate = renderState.data.currentDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -96,16 +81,8 @@ export const generateBiWeekChart = (monthPayload: ChartDataType, side: null | "l
} else if (side === "left") { } else if (side === "left") {
const currentDate = renderState.data.startDate; const currentDate = renderState.data.startDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -116,16 +93,8 @@ export const generateBiWeekChart = (monthPayload: ChartDataType, side: null | "l
} else if (side === "right") { } else if (side === "right") {
const currentDate = renderState.data.endDate; const currentDate = renderState.data.endDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() + 1,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);

View File

@ -10,8 +10,7 @@ export const getWeekNumberByDate = (date: Date) => {
const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000; const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000;
const weekStart = new Date(firstWeekStart); const weekStart = new Date(firstWeekStart);
const weekNumber = const weekNumber = Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
return weekNumber; return weekNumber;
}; };
@ -25,8 +24,7 @@ export const getNumberOfDaysInMonth = (month: number, year: number) => {
return date.getDate(); return date.getDate();
}; };
export const generateDate = (day: number, month: number, year: number) => export const generateDate = (day: number, month: number, year: number) => new Date(year, month, day);
new Date(year, month, day);
export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => { export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => {
const months = []; const months = [];
@ -45,8 +43,7 @@ export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => {
months.push(new Date(currentYear, currentMonth)); months.push(new Date(currentYear, currentMonth));
currentDate.setMonth(currentDate.getMonth() + 1); currentDate.setMonth(currentDate.getMonth() + 1);
} }
if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) months.push(endDate);
months.push(endDate);
return months; return months;
}; };
@ -73,9 +70,7 @@ export const getAllDaysInMonth = (month: number, year: number) => {
weekNumber: getWeekNumberByDate(date), weekNumber: getWeekNumberByDate(date),
title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`, title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`,
today: today:
currentDate.getFullYear() === year && currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === _day + 1
currentDate.getMonth() === month &&
currentDate.getDate() === _day + 1
? true ? true
: false, : false,
}); });
@ -99,10 +94,7 @@ export const generateMonthDataByMonth = (month: number, year: number) => {
return monthPayload; return monthPayload;
}; };
export const generateMonthDataByYear = ( export const generateMonthDataByYear = (monthPayload: ChartDataType, side: null | "left" | "right") => {
monthPayload: ChartDataType,
side: null | "left" | "right"
) => {
let renderState = monthPayload; let renderState = monthPayload;
const renderPayload: any = []; const renderPayload: any = [];
@ -114,16 +106,8 @@ export const generateMonthDataByYear = (
if (side === null) { if (side === null) {
const currentDate = renderState.data.currentDate; const currentDate = renderState.data.currentDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -138,16 +122,8 @@ export const generateMonthDataByYear = (
} else if (side === "left") { } else if (side === "left") {
const currentDate = renderState.data.startDate; const currentDate = renderState.data.startDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -158,16 +134,8 @@ export const generateMonthDataByYear = (
} else if (side === "right") { } else if (side === "right") {
const currentDate = renderState.data.endDate; const currentDate = renderState.data.endDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() + 1,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);

View File

@ -1,6 +1,5 @@
// Generating the date by using the year, month, and day // Generating the date by using the year, month, and day
export const generateDate = (day: number, month: number, year: number) => export const generateDate = (day: number, month: number, year: number) => new Date(year, month, day);
new Date(year, month, day);
// Getting the number of days in a month // Getting the number of days in a month
export const getNumberOfDaysInMonth = (month: number, year: number) => { export const getNumberOfDaysInMonth = (month: number, year: number) => {
@ -20,8 +19,7 @@ export const getWeekNumberByDate = (date: Date) => {
const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000; const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000;
const weekStart = new Date(firstWeekStart); const weekStart = new Date(firstWeekStart);
const weekNumber = const weekNumber = Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
return weekNumber; return weekNumber;
}; };
@ -86,8 +84,7 @@ export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => {
dates.push(new Date(currentYear, currentMonth)); dates.push(new Date(currentYear, currentMonth));
currentDate.setMonth(currentDate.getMonth() + 1); currentDate.setMonth(currentDate.getMonth() + 1);
} }
if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) dates.push(endDate);
dates.push(endDate);
return dates; return dates;
}; };

View File

@ -10,8 +10,7 @@ export const getWeekNumberByDate = (date: Date) => {
const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000; const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000;
const weekStart = new Date(firstWeekStart); const weekStart = new Date(firstWeekStart);
const weekNumber = const weekNumber = Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
return weekNumber; return weekNumber;
}; };
@ -25,8 +24,7 @@ export const getNumberOfDaysInMonth = (month: number, year: number) => {
return date.getDate(); return date.getDate();
}; };
export const generateDate = (day: number, month: number, year: number) => export const generateDate = (day: number, month: number, year: number) => new Date(year, month, day);
new Date(year, month, day);
export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => { export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => {
const months = []; const months = [];
@ -45,8 +43,7 @@ export const getDatesBetweenTwoDates = (startDate: Date, endDate: Date) => {
months.push(new Date(currentYear, currentMonth)); months.push(new Date(currentYear, currentMonth));
currentDate.setMonth(currentDate.getMonth() + 1); currentDate.setMonth(currentDate.getMonth() + 1);
} }
if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) if (endYear === currentDate.getFullYear() && endMonth === currentDate.getMonth()) months.push(endDate);
months.push(endDate);
return months; return months;
}; };
@ -73,9 +70,7 @@ export const getAllDaysInMonth = (month: number, year: number) => {
weekNumber: getWeekNumberByDate(date), weekNumber: getWeekNumberByDate(date),
title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`, title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`,
today: today:
currentDate.getFullYear() === year && currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === _day + 1
currentDate.getMonth() === month &&
currentDate.getDate() === _day + 1
? true ? true
: false, : false,
}); });
@ -99,10 +94,7 @@ export const generateMonthDataByMonth = (month: number, year: number) => {
return monthPayload; return monthPayload;
}; };
export const generateMonthDataByYear = ( export const generateMonthDataByYear = (monthPayload: ChartDataType, side: null | "left" | "right") => {
monthPayload: ChartDataType,
side: null | "left" | "right"
) => {
let renderState = monthPayload; let renderState = monthPayload;
const renderPayload: any = []; const renderPayload: any = [];
@ -114,16 +106,8 @@ export const generateMonthDataByYear = (
if (side === null) { if (side === null) {
const currentDate = renderState.data.currentDate; const currentDate = renderState.data.currentDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -138,16 +122,8 @@ export const generateMonthDataByYear = (
} else if (side === "left") { } else if (side === "left") {
const currentDate = renderState.data.startDate; const currentDate = renderState.data.startDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -158,16 +134,8 @@ export const generateMonthDataByYear = (
} else if (side === "right") { } else if (side === "right") {
const currentDate = renderState.data.endDate; const currentDate = renderState.data.endDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() + 1,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);

View File

@ -3,12 +3,7 @@ import { ChartDataType, IGanttBlock } from "../types";
// data // data
import { weeks, months } from "../data"; import { weeks, months } from "../data";
// helpers // helpers
import { import { generateDate, getWeekNumberByDate, getNumberOfDaysInMonth, getDatesBetweenTwoDates } from "./helpers";
generateDate,
getWeekNumberByDate,
getNumberOfDaysInMonth,
getDatesBetweenTwoDates,
} from "./helpers";
type GetAllDaysInMonthInMonthViewType = { type GetAllDaysInMonthInMonthViewType = {
date: any; date: any;
@ -62,9 +57,7 @@ const getAllDaysInMonthInMonthView = (month: number, year: number): IMonthChild[
title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`, title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`,
active: false, active: false,
today: today:
currentDate.getFullYear() === year && currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === _day + 1
currentDate.getMonth() === month &&
currentDate.getDate() === _day + 1
? true ? true
: false, : false,
}); });
@ -100,16 +93,8 @@ export const generateMonthChart = (monthPayload: ChartDataType, side: null | "le
if (side === null) { if (side === null) {
const currentDate = renderState.data.currentDate; const currentDate = renderState.data.currentDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -124,16 +109,8 @@ export const generateMonthChart = (monthPayload: ChartDataType, side: null | "le
} else if (side === "left") { } else if (side === "left") {
const currentDate = renderState.data.startDate; const currentDate = renderState.data.startDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -144,16 +121,8 @@ export const generateMonthChart = (monthPayload: ChartDataType, side: null | "le
} else if (side === "right") { } else if (side === "right") {
const currentDate = renderState.data.endDate; const currentDate = renderState.data.endDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() + 1,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -191,10 +160,7 @@ export const getNumberOfDaysBetweenTwoDatesInMonth = (startDate: Date, endDate:
}; };
// calc item scroll position and width // calc item scroll position and width
export const getMonthChartItemPositionWidthInMonth = ( export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType, itemData: IGanttBlock) => {
chartData: ChartDataType,
itemData: IGanttBlock
) => {
let scrollPosition: number = 0; let scrollPosition: number = 0;
let scrollWidth: number = 0; let scrollWidth: number = 0;
@ -207,9 +173,7 @@ export const getMonthChartItemPositionWidthInMonth = (
// position code starts // position code starts
const positionTimeDifference: number = startDate.getTime() - itemStartDate.getTime(); const positionTimeDifference: number = startDate.getTime() - itemStartDate.getTime();
const positionDaysDifference: number = Math.abs( const positionDaysDifference: number = Math.abs(Math.floor(positionTimeDifference / (1000 * 60 * 60 * 24)));
Math.floor(positionTimeDifference / (1000 * 60 * 60 * 24))
);
scrollPosition = positionDaysDifference * chartData.data.width; scrollPosition = positionDaysDifference * chartData.data.width;
var diffMonths = (itemStartDate.getFullYear() - startDate.getFullYear()) * 12; var diffMonths = (itemStartDate.getFullYear() - startDate.getFullYear()) * 12;
@ -221,9 +185,7 @@ export const getMonthChartItemPositionWidthInMonth = (
// width code starts // width code starts
const widthTimeDifference: number = itemStartDate.getTime() - itemTargetDate.getTime(); const widthTimeDifference: number = itemStartDate.getTime() - itemTargetDate.getTime();
const widthDaysDifference: number = Math.abs( const widthDaysDifference: number = Math.abs(Math.floor(widthTimeDifference / (1000 * 60 * 60 * 24)));
Math.floor(widthTimeDifference / (1000 * 60 * 60 * 24))
);
scrollWidth = (widthDaysDifference + 1) * chartData.data.width + 1; scrollWidth = (widthDaysDifference + 1) * chartData.data.width + 1;
// width code ends // width code ends

View File

@ -36,10 +36,7 @@ const generateMonthDataByMonthAndYearInMonthView = (month: number, year: number)
return weekPayload; return weekPayload;
}; };
export const generateQuarterChart = ( export const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" | "right") => {
quarterPayload: ChartDataType,
side: null | "left" | "right"
) => {
let renderState = quarterPayload; let renderState = quarterPayload;
const renderPayload: any = []; const renderPayload: any = [];

View File

@ -3,12 +3,7 @@ import { ChartDataType } from "../types";
// data // data
import { weeks, months } from "../data"; import { weeks, months } from "../data";
// helpers // helpers
import { import { generateDate, getWeekNumberByDate, getNumberOfDaysInMonth, getDatesBetweenTwoDates } from "./helpers";
generateDate,
getWeekNumberByDate,
getNumberOfDaysInMonth,
getDatesBetweenTwoDates,
} from "./helpers";
type GetAllDaysInMonthInMonthViewType = { type GetAllDaysInMonthInMonthViewType = {
date: any; date: any;
@ -34,9 +29,7 @@ const getAllDaysInMonthInMonthView = (month: number, year: number) => {
title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`, title: `${weeks[date.getDay()].shortTitle} ${_day + 1}`,
active: false, active: false,
today: today:
currentDate.getFullYear() === year && currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === _day + 1
currentDate.getMonth() === month &&
currentDate.getDate() === _day + 1
? true ? true
: false, : false,
}); });
@ -72,16 +65,8 @@ export const generateWeekChart = (monthPayload: ChartDataType, side: null | "lef
if (side === null) { if (side === null) {
const currentDate = renderState.data.currentDate; const currentDate = renderState.data.currentDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -96,16 +81,8 @@ export const generateWeekChart = (monthPayload: ChartDataType, side: null | "lef
} else if (side === "left") { } else if (side === "left") {
const currentDate = renderState.data.startDate; const currentDate = renderState.data.startDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
currentDate.getMonth() - range,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);
@ -116,16 +93,8 @@ export const generateWeekChart = (monthPayload: ChartDataType, side: null | "lef
} else if (side === "right") { } else if (side === "right") {
const currentDate = renderState.data.endDate; const currentDate = renderState.data.endDate;
minusDate = new Date( minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
currentDate.getFullYear(), plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
currentDate.getMonth() + 1,
currentDate.getDate()
);
plusDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + range,
currentDate.getDate()
);
if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate); if (minusDate && plusDate) filteredDates = getDatesBetweenTwoDates(minusDate, plusDate);

View File

@ -31,6 +31,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
cycle: cycleStore, cycle: cycleStore,
cycleIssueFilter: cycleIssueFilterStore, cycleIssueFilter: cycleIssueFilterStore,
project: projectStore, project: projectStore,
projectState: projectStateStore,
commandPalette: commandPaletteStore, commandPalette: commandPaletteStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectDetails } = projectStore; const { currentProjectDetails } = projectStore;
@ -178,7 +179,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
} }
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -31,6 +31,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
module: moduleStore, module: moduleStore,
moduleFilter: moduleFilterStore, moduleFilter: moduleFilterStore,
project: projectStore, project: projectStore,
projectState: projectStateStore,
commandPalette: commandPaletteStore, commandPalette: commandPaletteStore,
} = useMobxStore(); } = useMobxStore();
const activeLayout = issueFilterStore.userDisplayFilters.layout; const activeLayout = issueFilterStore.userDisplayFilters.layout;
@ -177,7 +178,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
} }
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -5,10 +5,8 @@ import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
// helper
import { truncateText } from "helpers/string.helper";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, LayersIcon } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// icons // icons
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
// components // components
@ -22,7 +20,11 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { project: projectStore, archivedIssueFilters: archivedIssueFiltersStore } = useMobxStore(); const {
project: projectStore,
archivedIssueFilters: archivedIssueFiltersStore,
projectState: projectStateStore,
} = useMobxStore();
const { currentProjectDetails } = projectStore; const { currentProjectDetails } = projectStore;
@ -118,7 +120,7 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
} }
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -26,6 +26,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
const { const {
issueFilter: issueFilterStore, issueFilter: issueFilterStore,
project: projectStore, project: projectStore,
projectState: projectStateStore,
inbox: inboxStore, inbox: inboxStore,
commandPalette: commandPaletteStore, commandPalette: commandPaletteStore,
} = useMobxStore(); } = useMobxStore();
@ -172,7 +173,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
} }
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -23,6 +23,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
issueFilter: issueFilterStore, issueFilter: issueFilterStore,
projectViewFilters: projectViewFiltersStore, projectViewFilters: projectViewFiltersStore,
project: projectStore, project: projectStore,
projectState: projectStateStore,
projectViews: projectViewsStore, projectViews: projectViewsStore,
} = useMobxStore(); } = useMobxStore();
@ -163,7 +164,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
} }
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const CompletedCycleIcon: React.FC<Props> = ({ export const CompletedCycleIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "black" }) => (
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}> <svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}>
<path <path
d="m21.65 36.6-6.9-6.85 2.1-2.1 4.8 4.7 9.2-9.2 2.1 2.15ZM6 44V7h6.25V4h3.25v3h17V4h3.25v3H42v37Zm3-3h30V19.5H9Zm0-24.5h30V10H9Zm0 0V10v6.5Z" d="m21.65 36.6-6.9-6.85 2.1-2.1 4.8 4.7 9.2-9.2 2.1 2.15ZM6 44V7h6.25V4h3.25v3h17V4h3.25v3H42v37Zm3-3h30V19.5H9Zm0-24.5h30V10H9Zm0 0V10v6.5Z"

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const CurrentCycleIcon: React.FC<Props> = ({ export const CurrentCycleIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "black" }) => (
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}> <svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}>
<path <path
d="M15.3 28.3q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575Zm8.85 0q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575Zm8.5 0q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575ZM6 44V7h6.25V4h3.25v3h17V4h3.25v3H42v37Zm3-3h30V19.5H9Zm0-24.5h30V10H9Zm0 0V10v6.5Z" d="M15.3 28.3q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575Zm8.85 0q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575Zm8.5 0q-.85 0-1.425-.575-.575-.575-.575-1.425 0-.85.575-1.425.575-.575 1.425-.575.85 0 1.425.575.575.575.575 1.425 0 .85-.575 1.425-.575.575-1.425.575ZM6 44V7h6.25V4h3.25v3h17V4h3.25v3H42v37Zm3-3h30V19.5H9Zm0-24.5h30V10H9Zm0 0V10v6.5Z"

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const DocumentIcon: React.FC<Props> = ({ export const DocumentIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => (
width = "24",
height = "24",
className,
color,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const ExternalLinkIcon: React.FC<Props> = ({ export const ExternalLinkIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "black" }) => (
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -84,4 +84,4 @@ export * from "./clock-icon";
export * from "./bell-icon"; export * from "./bell-icon";
export * from "./single-comment-icon"; export * from "./single-comment-icon";
export * from "./related-icon"; export * from "./related-icon";
export * from "./module-icon"; export * from "./module-icon";

View File

@ -7,11 +7,7 @@ type Props = {
color?: string; color?: string;
}; };
export const ModuleCancelledIcon: React.FC<Props> = ({ export const ModuleCancelledIcon: React.FC<Props> = ({ width = "20", height = "20", className }) => (
width = "20",
height = "20",
className,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -7,11 +7,7 @@ type Props = {
color?: string; color?: string;
}; };
export const ModuleCompletedIcon: React.FC<Props> = ({ export const ModuleCompletedIcon: React.FC<Props> = ({ width = "20", height = "20", className }) => (
width = "20",
height = "20",
className,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -7,11 +7,7 @@ type Props = {
color?: string; color?: string;
}; };
export const ModuleInProgressIcon: React.FC<Props> = ({ export const ModuleInProgressIcon: React.FC<Props> = ({ width = "20", height = "20", className }) => (
width = "20",
height = "20",
className,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}
@ -23,10 +19,7 @@ export const ModuleInProgressIcon: React.FC<Props> = ({
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">
<path fill="#f7b964" d="M0,111.14c.63.7.21,1.53.3,2.29-.07.26-.17.28-.3,0Z" /> <path fill="#f7b964" d="M0,111.14c.63.7.21,1.53.3,2.29-.07.26-.17.28-.3,0Z" />
<path fill="#f6ab3e" d="M0,119.46a3.11,3.11,0,0,1,.3,2q-.19.33-.3,0Z" /> <path fill="#f6ab3e" d="M0,119.46a3.11,3.11,0,0,1,.3,2q-.19.33-.3,0Z" />
<path <path fill="#facf96" d="M.27,123.16c0,.66.38,1.38-.27,2v-2C.13,122.89.22,122.91.27,123.16Z" />
fill="#facf96"
d="M.27,123.16c0,.66.38,1.38-.27,2v-2C.13,122.89.22,122.91.27,123.16Z"
/>
<path fill="#f5a939" d="M0,113.47l.3,0a2.39,2.39,0,0,1-.3,1.71Z" /> <path fill="#f5a939" d="M0,113.47l.3,0a2.39,2.39,0,0,1-.3,1.71Z" />
<path fill="#f8ba67" d="M.27,123.16a.63.63,0,0,1-.27,0v-1.66l.3,0Z" /> <path fill="#f8ba67" d="M.27,123.16a.63.63,0,0,1-.27,0v-1.66l.3,0Z" />
<path <path

View File

@ -17,21 +17,12 @@ type Props = {
width?: string; width?: string;
}; };
export const ModuleStatusIcon: React.FC<Props> = ({ export const ModuleStatusIcon: React.FC<Props> = ({ status, className, height = "12px", width = "12px" }) => {
status, if (status === "backlog") return <ModuleBacklogIcon className={className} height={height} width={width} />;
className, else if (status === "cancelled") return <ModuleCancelledIcon className={className} height={height} width={width} />;
height = "12px", else if (status === "completed") return <ModuleCompletedIcon className={className} height={height} width={width} />;
width = "12px",
}) => {
if (status === "backlog")
return <ModuleBacklogIcon className={className} height={height} width={width} />;
else if (status === "cancelled")
return <ModuleCancelledIcon className={className} height={height} width={width} />;
else if (status === "completed")
return <ModuleCompletedIcon className={className} height={height} width={width} />;
else if (status === "in-progress") else if (status === "in-progress")
return <ModuleInProgressIcon className={className} height={height} width={width} />; return <ModuleInProgressIcon className={className} height={height} width={width} />;
else if (status === "paused") else if (status === "paused") return <ModulePausedIcon className={className} height={height} width={width} />;
return <ModulePausedIcon className={className} height={height} width={width} />;
else return <ModulePlannedIcon className={className} height={height} width={width} />; else return <ModulePlannedIcon className={className} height={height} width={width} />;
}; };

View File

@ -2,19 +2,8 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const PencilScribbleIcon: React.FC<Props> = ({ export const PencilScribbleIcon: React.FC<Props> = ({ width = "20", height = "20", className, color = "#000000" }) => (
width = "20", <svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960">
height = "20",
className,
color = "#000000",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 96 960 960"
>
<path <path
d="M560 936q-12 0-21-9t-9-21q0-13 9-21.5t21-8.5q59 0 99.5-24t40.5-56q0-23-29.5-45T591 717l47-47q63 19 92.5 52.5T760 796q0 67-61 103.5T560 936ZM240 642q-64-14-92-44t-28-62q0-35 26-63t120-62q66-24 85-39t19-35q0-25-22-43t-68-18q-27 0-46 7t-34 22q-8 8-20.5 9.5T157 308q-11-8-11.5-20t7.5-21q17-22 51-36.5t76-14.5q68 0 109 32.5t41 88.5q0 41-28.5 69.5T290 466q-67 25-88.5 39.5T180 536q0 16 27 30.5t81 27.5l-48 48Zm496-154L608 360l45-45q18-18 40-18t40 18l48 48q18 18 18 40t-18 40l-45 45ZM220 876h42l345-345-42-42-345 345v42Zm-60 60V808l405-405 128 128-405 405H160Zm405-447 42 42-42-42Z" d="M560 936q-12 0-21-9t-9-21q0-13 9-21.5t21-8.5q59 0 99.5-24t40.5-56q0-23-29.5-45T591 717l47-47q63 19 92.5 52.5T760 796q0 67-61 103.5T560 936ZM240 642q-64-14-92-44t-28-62q0-35 26-63t120-62q66-24 85-39t19-35q0-25-22-43t-68-18q-27 0-46 7t-34 22q-8 8-20.5 9.5T157 308q-11-8-11.5-20t7.5-21q17-22 51-36.5t76-14.5q68 0 109 32.5t41 88.5q0 41-28.5 69.5T290 466q-67 25-88.5 39.5T180 536q0 16 27 30.5t81 27.5l-48 48Zm496-154L608 360l45-45q18-18 40-18t40 18l48 48q18 18 18 40t-18 40l-45 45ZM220 876h42l345-345-42-42-345 345v42Zm-60 60V808l405-405 128 128-405 405H160Zm405-447 42 42-42-42Z"
fill={color} fill={color}

View File

@ -2,11 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const QuestionMarkCircleIcon: React.FC<Props> = ({ export const QuestionMarkCircleIcon: React.FC<Props> = ({ width = "24", height = "24", className }) => (
width = "24",
height = "24",
className,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const SingleCommentCard: React.FC<Props> = ({ export const SingleCommentCard: React.FC<Props> = ({ width = "24", height = "24", className, color }) => (
width = "24",
height = "24",
className,
color,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const TagIcon: React.FC<Props> = ({ export const TagIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "black" }) => (
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -2,11 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const TriangleExclamationIcon: React.FC<Props> = ({ export const TriangleExclamationIcon: React.FC<Props> = ({ width = "24", height = "24", className }) => (
width = "24",
height = "24",
className,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const UpcomingCycleIcon: React.FC<Props> = ({ export const UpcomingCycleIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "black" }) => (
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}> <svg xmlns="http://www.w3.org/2000/svg" height={height} width={width} className={className}>
<path <path
d="M28.3 44v-3H39V19.5H9v11H6V10q0-1.2.9-2.1Q7.8 7 9 7h3.25V4h3.25v3h17V4h3.25v3H39q1.2 0 2.1.9.9.9.9 2.1v31q0 1.2-.9 2.1-.9.9-2.1.9ZM16 47.3l-2.1-2.1 5.65-5.7H2.5v-3h17.05l-5.65-5.7 2.1-2.1 9.3 9.3ZM9 16.5h30V10H9Zm0 0V10v6.5Z" d="M28.3 44v-3H39V19.5H9v11H6V10q0-1.2.9-2.1Q7.8 7 9 7h3.25V4h3.25v3h17V4h3.25v3H39q1.2 0 2.1.9.9.9.9 2.1v31q0 1.2-.9 2.1-.9.9-2.1.9ZM16 47.3l-2.1-2.1 5.65-5.7H2.5v-3h17.05l-5.65-5.7 2.1-2.1 9.3 9.3ZM9 16.5h30V10H9Zm0 0V10v6.5Z"

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const UserGroupIcon: React.FC<Props> = ({ export const UserGroupIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => (
width = "24",
height = "24",
className,
color,
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}

View File

@ -41,7 +41,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const editorSuggestion = useEditorSuggestions() const editorSuggestion = useEditorSuggestions();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query; const { workspaceSlug, projectId, inboxId } = router.query;

View File

@ -37,9 +37,7 @@ export const JiraConfirmImport: React.FC = () => {
<p className="text-sm text-custom-text-200">Labels</p> <p className="text-sm text-custom-text-200">Labels</p>
</div> </div>
<div> <div>
<h4 className="mb-2 text-lg font-semibold"> <h4 className="mb-2 text-lg font-semibold">{watch("data.users").filter((user) => user.import).length}</h4>
{watch("data.users").filter((user) => user.import).length}
</h4>
<p className="text-sm text-custom-text-200">User</p> <p className="text-sm text-custom-text-200">User</p>
</div> </div>
</div> </div>

View File

@ -15,7 +15,7 @@ import { X } from "lucide-react";
// helpers // helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types // types
import { IIssueFilterOptions, IIssueLabels, IProject, IStateResponse, IUserLite } from "types"; import { IIssueFilterOptions, IIssueLabels, IProject, IState, IUserLite } from "types";
type Props = { type Props = {
appliedFilters: IIssueFilterOptions; appliedFilters: IIssueFilterOptions;
@ -24,7 +24,7 @@ type Props = {
labels?: IIssueLabels[] | undefined; labels?: IIssueLabels[] | undefined;
members?: IUserLite[] | undefined; members?: IUserLite[] | undefined;
projects?: IProject[] | undefined; projects?: IProject[] | undefined;
states?: IStateResponse | undefined; states?: IState[] | undefined;
}; };
const membersFilters = ["assignees", "mentions", "created_by", "subscriber"]; const membersFilters = ["assignees", "mentions", "created_by", "subscriber"];
@ -70,7 +70,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
{filterKey === "priority" && ( {filterKey === "priority" && (
<AppliedPriorityFilters handleRemove={(val) => handleRemoveFilter("priority", val)} values={value} /> <AppliedPriorityFilters handleRemove={(val) => handleRemoveFilter("priority", val)} values={value} />
)} )}
{filterKey === "state" && ( {filterKey === "state" && states && (
<AppliedStateFilters <AppliedStateFilters
handleRemove={(val) => handleRemoveFilter("state", val)} handleRemove={(val) => handleRemoveFilter("state", val)}
states={states} states={states}

View File

@ -12,7 +12,11 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { archivedIssueFilters: archivedIssueFiltersStore, project: projectStore } = useMobxStore(); const {
archivedIssueFilters: archivedIssueFiltersStore,
project: projectStore,
projectState: projectStateStore,
} = useMobxStore();
const userFilters = archivedIssueFiltersStore.userFilters; const userFilters = archivedIssueFiltersStore.userFilters;
@ -74,7 +78,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]} states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/> />
</div> </div>
); );

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
@ -12,7 +11,11 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
const { project: projectStore, cycleIssueFilter: cycleIssueFilterStore } = useMobxStore(); const {
project: projectStore,
cycleIssueFilter: cycleIssueFilterStore,
projectState: projectStateStore,
} = useMobxStore();
const userFilters = cycleIssueFilterStore.cycleFilters; const userFilters = cycleIssueFilterStore.cycleFilters;
@ -70,7 +73,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]} states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/> />
</div> </div>
); );

View File

@ -12,7 +12,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
const { project: projectStore, moduleFilter: moduleFilterStore } = useMobxStore(); const { project: projectStore, moduleFilter: moduleFilterStore, projectState: projectStateStore } = useMobxStore();
const userFilters = moduleFilterStore.moduleFilters; const userFilters = moduleFilterStore.moduleFilters;
@ -70,7 +70,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]} states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/> />
</div> </div>
); );

View File

@ -12,7 +12,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore, projectState: projectStateStore } = useMobxStore();
const userFilters = issueFilterStore.userFilters; const userFilters = issueFilterStore.userFilters;
@ -74,7 +74,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]} states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/> />
</div> </div>
); );

View File

@ -19,6 +19,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
const { const {
project: projectStore, project: projectStore,
projectState: projectStateStore,
projectViews: projectViewsStore, projectViews: projectViewsStore,
projectViewFilters: projectViewFiltersStore, projectViewFilters: projectViewFiltersStore,
} = useMobxStore(); } = useMobxStore();
@ -99,7 +100,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]} states={projectStateStore.states?.[projectId?.toString() ?? ""]}
/> />
{storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data ?? {}) && ( {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data ?? {}) && (
<Button variant="primary" size="sm" onClick={handleUpdateView}> <Button variant="primary" size="sm" onClick={handleUpdateView}>

View File

@ -3,26 +3,22 @@ import { observer } from "mobx-react-lite";
// icons // icons
import { StateGroupIcon } from "@plane/ui"; import { StateGroupIcon } from "@plane/ui";
import { X } from "lucide-react"; import { X } from "lucide-react";
// helpers
import { getStatesList } from "helpers/state.helper";
// types // types
import { IStateResponse } from "types"; import { IState } from "types";
type Props = { type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
states: IStateResponse | undefined; states: IState[];
values: string[]; values: string[];
}; };
export const AppliedStateFilters: React.FC<Props> = observer((props) => { export const AppliedStateFilters: React.FC<Props> = observer((props) => {
const { handleRemove, states, values } = props; const { handleRemove, states, values } = props;
const statesList = getStatesList(states);
return ( return (
<div className="flex items-center gap-1 flex-wrap"> <div className="flex items-center gap-1 flex-wrap">
{values.map((stateId) => { {values.map((stateId) => {
const stateDetails = statesList?.find((s) => s.id === stateId); const stateDetails = states?.find((s) => s.id === stateId);
if (!stateDetails) return null; if (!stateDetails) return null;

View File

@ -15,7 +15,7 @@ import {
FilterTargetDate, FilterTargetDate,
} from "components/issues"; } from "components/issues";
// types // types
import { IIssueFilterOptions, IIssueLabels, IProject, IStateResponse, IUserLite } from "types"; import { IIssueFilterOptions, IIssueLabels, IProject, IState, IUserLite } from "types";
// constants // constants
import { ILayoutDisplayFiltersOptions } from "constants/issue"; import { ILayoutDisplayFiltersOptions } from "constants/issue";
@ -26,7 +26,7 @@ type Props = {
labels?: IIssueLabels[] | undefined; labels?: IIssueLabels[] | undefined;
members?: IUserLite[] | undefined; members?: IUserLite[] | undefined;
projects?: IProject[] | undefined; projects?: IProject[] | undefined;
states?: IStateResponse | undefined; states?: IState[] | undefined;
}; };
export const FilterSelection: React.FC<Props> = observer((props) => { export const FilterSelection: React.FC<Props> = observer((props) => {

View File

@ -1,19 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
// components // components
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
// ui // ui
import { Loader, StateGroupIcon } from "@plane/ui"; import { Loader, StateGroupIcon } from "@plane/ui";
// helpers
import { getStatesList } from "helpers/state.helper";
// types // types
import { IStateResponse } from "types"; import { IState } from "types";
type Props = { type Props = {
appliedFilters: string[] | null; appliedFilters: string[] | null;
handleUpdate: (val: string) => void; handleUpdate: (val: string) => void;
searchQuery: string; searchQuery: string;
states: IStateResponse | undefined; states: IState[] | undefined;
}; };
export const FilterState: React.FC<Props> = (props) => { export const FilterState: React.FC<Props> = (props) => {
@ -22,11 +19,9 @@ export const FilterState: React.FC<Props> = (props) => {
const [itemsToRender, setItemsToRender] = useState(5); const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true); const [previewEnabled, setPreviewEnabled] = useState(true);
const statesList = getStatesList(states);
const appliedFiltersCount = appliedFilters?.length ?? 0; const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = statesList?.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase())); const filteredOptions = states?.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()));
const handleViewToggle = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!filteredOptions) return;

Some files were not shown because too many files have changed in this diff Show More