mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: project states fixes (#2731)
* fix: project states fixes * fix: states fixes * fix: formating all files
This commit is contained in:
parent
bd1a850f35
commit
20fb79567f
@ -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.
|
||||||
|
18
ENV_SETUP.md
18
ENV_SETUP.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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:";
|
||||||
}
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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({
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as default } from "./table-cell"
|
export { default as default } from "./table-cell";
|
||||||
|
@ -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,
|
||||||
]
|
];
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as default } from "./table-header"
|
export { default as default } from "./table-header";
|
||||||
|
@ -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,
|
||||||
]
|
];
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as default } from "./table-row"
|
export { default as default } from "./table-row";
|
||||||
|
@ -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,
|
||||||
]
|
];
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
@ -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}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default as default } from "./table"
|
export { default as default } from "./table";
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -95,4 +95,3 @@ export const useEditor = ({
|
|||||||
|
|
||||||
return editor;
|
return editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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)];
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
@ -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),
|
||||||
|
});
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
@ -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`,
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
@ -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";
|
||||||
|
@ -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";
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
@ -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";
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -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);
|
||||||
|
@ -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">
|
||||||
|
@ -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 };
|
||||||
|
@ -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))",
|
||||||
|
@ -58,7 +58,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Button.displayName = "plane-ui-button";
|
Button.displayName = "plane-ui-button";
|
||||||
|
@ -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];
|
||||||
|
@ -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 = {
|
||||||
|
@ -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, {
|
||||||
|
@ -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";
|
||||||
|
@ -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 };
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
@ -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] : []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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}`
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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);
|
||||||
|
@ -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) => {
|
||||||
|
@ -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]));
|
||||||
|
@ -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}>
|
||||||
|
@ -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 && (
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 = [];
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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";
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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}>
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user