From eed2ca77ef2a00a46aaff806606d4bbafc18695b Mon Sep 17 00:00:00 2001
From: Akshita Goyal <36129505+gakshita@users.noreply.github.com>
Date: Tue, 5 Nov 2024 16:07:27 +0530
Subject: [PATCH 1/7] fix: added workspaceslug in renderChildren of project
settings (#5951)
* fix: added workspaceslug in renderChildren of project settings
* fix: updated apis
* fix: types
* fix: added editor
* fix: handled avatar for intake
---
packages/types/src/inbox.d.ts | 3 +-
.../components/editor/rich-text-editor.tsx | 41 +++++++++++++++++++
space/core/store/issue-detail.store.ts | 19 +++++++++
space/core/types/intake.d.ts | 6 +++
.../constants/project/settings/features.tsx | 3 +-
.../inbox/sidebar/inbox-list-item.tsx | 9 +++-
.../project/settings/features-list.tsx | 2 +-
.../services/inbox/inbox-issue.service.ts | 8 ++--
web/core/store/inbox/project-inbox.store.ts | 15 ++++---
9 files changed, 90 insertions(+), 16 deletions(-)
create mode 100644 space/core/components/editor/rich-text-editor.tsx
create mode 100644 space/core/types/intake.d.ts
diff --git a/packages/types/src/inbox.d.ts b/packages/types/src/inbox.d.ts
index bb60b35a21..5ae6c160e8 100644
--- a/packages/types/src/inbox.d.ts
+++ b/packages/types/src/inbox.d.ts
@@ -100,7 +100,8 @@ export type TInboxIssueWithPagination = TInboxIssuePaginationInfo & {
export type TInboxForm = {
anchor: string;
id: string;
- is_disabled: boolean;
+ is_in_app_enabled: boolean;
+ is_form_enabled: boolean;
};
export type TInboxIssueForm = {
diff --git a/space/core/components/editor/rich-text-editor.tsx b/space/core/components/editor/rich-text-editor.tsx
new file mode 100644
index 0000000000..af4cf97136
--- /dev/null
+++ b/space/core/components/editor/rich-text-editor.tsx
@@ -0,0 +1,41 @@
+import React, { forwardRef } from "react";
+// editor
+import { EditorRefApi, IMentionHighlight, IRichTextEditor, RichTextEditorWithRef } from "@plane/editor";
+// types
+// helpers
+import { cn } from "@/helpers/common.helper";
+import { getEditorFileHandlers } from "@/helpers/editor.helper";
+
+interface RichTextEditorWrapperProps extends Omit {
+ uploadFile: (file: File) => Promise;
+}
+
+export const RichTextEditor = forwardRef((props, ref) => {
+ const { containerClassName, uploadFile, ...rest } = props;
+ // store hooks
+
+ // use-mention
+
+ // file size
+
+ return (
+ {
+ throw new Error("Function not implemented.");
+ },
+ suggestions: undefined,
+ }}
+ ref={ref}
+ fileHandler={getEditorFileHandlers({
+ uploadFile,
+ workspaceId: "",
+ anchor: "",
+ })}
+ {...rest}
+ containerClassName={cn("relative pl-3 pb-3", containerClassName)}
+ />
+ );
+});
+
+RichTextEditor.displayName = "RichTextEditor";
diff --git a/space/core/store/issue-detail.store.ts b/space/core/store/issue-detail.store.ts
index ee8a3031ed..31058f790f 100644
--- a/space/core/store/issue-detail.store.ts
+++ b/space/core/store/issue-detail.store.ts
@@ -36,6 +36,7 @@ export interface IIssueDetailStore {
updateIssueComment: (anchor: string, issueID: string, commentID: string, data: any) => Promise;
deleteIssueComment: (anchor: string, issueID: string, commentID: string) => void;
uploadCommentAsset: (file: File, anchor: string, commentID?: string) => Promise;
+ uploadIssueAsset: (file: File, anchor: string, commentID?: string) => Promise;
addCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
removeCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
// reaction actions
@@ -79,6 +80,7 @@ export class IssueDetailStore implements IIssueDetailStore {
updateIssueComment: action,
deleteIssueComment: action,
uploadCommentAsset: action,
+ uploadIssueAsset: action,
addCommentReaction: action,
removeCommentReaction: action,
// reaction actions
@@ -245,6 +247,23 @@ export class IssueDetailStore implements IIssueDetailStore {
}
};
+ uploadIssueAsset = async (file: File, anchor: string, commentID?: string) => {
+ try {
+ const res = await this.fileService.uploadAsset(
+ anchor,
+ {
+ entity_identifier: commentID ?? "",
+ entity_type: EFileAssetType.ISSUE_ATTACHMENT,
+ },
+ file
+ );
+ return res;
+ } catch (error) {
+ console.log("Error in uploading comment asset:", error);
+ throw new Error("Asset upload failed. Please try again later.");
+ }
+ };
+
addCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => {
const newReaction = {
id: uuidv4(),
diff --git a/space/core/types/intake.d.ts b/space/core/types/intake.d.ts
new file mode 100644
index 0000000000..9cf2934f63
--- /dev/null
+++ b/space/core/types/intake.d.ts
@@ -0,0 +1,6 @@
+export type TIntakeIssueForm = {
+ name: string;
+ email: string;
+ username: string;
+ description_html: string;
+};
diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx
index b9b39700d6..b3601e27b1 100644
--- a/web/ce/constants/project/settings/features.tsx
+++ b/web/ce/constants/project/settings/features.tsx
@@ -13,7 +13,8 @@ export type TProperties = {
renderChildren?: (
currentProjectDetails: IProject,
isAdmin: boolean,
- handleSubmit: (featureKey: string, featureProperty: string) => Promise
+ handleSubmit: (featureKey: string, featureProperty: string) => Promise,
+ workspaceSlug: string
) => ReactNode;
};
export type TFeatureList = {
diff --git a/web/core/components/inbox/sidebar/inbox-list-item.tsx b/web/core/components/inbox/sidebar/inbox-list-item.tsx
index c1574fe29e..6762c25280 100644
--- a/web/core/components/inbox/sidebar/inbox-list-item.tsx
+++ b/web/core/components/inbox/sidebar/inbox-list-item.tsx
@@ -4,7 +4,7 @@ import { FC, MouseEvent } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
-import { Tooltip, PriorityIcon, Row } from "@plane/ui";
+import { Tooltip, PriorityIcon, Row, Avatar } from "@plane/ui";
// components
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
import { InboxIssueStatus } from "@/components/inbox";
@@ -12,6 +12,7 @@ import { InboxIssueStatus } from "@/components/inbox";
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
+import { getFileURL } from "@/helpers/file.helper";
import { useLabel, useMember, useProjectInbox } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
@@ -116,7 +117,11 @@ export const InboxIssueListItem: FC = observer((props)
)}
{/* created by */}
- {createdByDetails && }
+ {createdByDetails && createdByDetails.email?.includes("intake@plane.so") ? (
+
+ ) : createdByDetails ? (
+
+ ) : null}
diff --git a/web/core/components/project/settings/features-list.tsx b/web/core/components/project/settings/features-list.tsx
index f1f17f2d59..7bd0a15e6d 100644
--- a/web/core/components/project/settings/features-list.tsx
+++ b/web/core/components/project/settings/features-list.tsx
@@ -102,7 +102,7 @@ export const ProjectFeaturesList: FC = observer((props) => {
{currentProjectDetails?.[featureItem.property as keyof IProject] &&
featureItem.renderChildren &&
- featureItem.renderChildren(currentProjectDetails, isAdmin, handleSubmit)}
+ featureItem.renderChildren(currentProjectDetails, isAdmin, handleSubmit, workspaceSlug)}
);
diff --git a/web/core/services/inbox/inbox-issue.service.ts b/web/core/services/inbox/inbox-issue.service.ts
index 61aaeb8490..b1bf74999f 100644
--- a/web/core/services/inbox/inbox-issue.service.ts
+++ b/web/core/services/inbox/inbox-issue.service.ts
@@ -77,17 +77,15 @@ export class InboxIssueService extends APIService {
}
async retrievePublishForm(workspaceSlug: string, projectId: string): Promise {
- return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/publish-intake/`)
+ return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/intake-settings/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
- async updatePublishForm(workspaceSlug: string, projectId: string, is_disabled: boolean): Promise {
- return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/publish-intake/`, {
- is_disabled,
- })
+ async updatePublishForm(workspaceSlug: string, projectId: string, data: Partial): Promise {
+ return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/intake-settings/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
diff --git a/web/core/store/inbox/project-inbox.store.ts b/web/core/store/inbox/project-inbox.store.ts
index f6953debf8..9960faa154 100644
--- a/web/core/store/inbox/project-inbox.store.ts
+++ b/web/core/store/inbox/project-inbox.store.ts
@@ -71,7 +71,7 @@ export interface IProjectInboxStore {
fetchInboxPaginationIssues: (workspaceSlug: string, projectId: string) => Promise;
fetchInboxIssueById: (workspaceSlug: string, projectId: string, inboxIssueId: string) => Promise;
fetchIntakeForms: (workspaceSlug: string, projectId: string) => Promise;
- toggleIntakeForms: (workspaceSlug: string, projectId: string, isDisabled: boolean) => Promise;
+ toggleIntakeForms: (workspaceSlug: string, projectId: string, data: Partial) => Promise;
regenerateIntakeForms: (workspaceSlug: string, projectId: string) => Promise;
createInboxIssue: (
workspaceSlug: string,
@@ -329,20 +329,23 @@ export class ProjectInboxStore implements IProjectInboxStore {
}
};
- toggleIntakeForms = async (workspaceSlug: string, projectId: string, isDisabled: boolean) => {
+ toggleIntakeForms = async (workspaceSlug: string, projectId: string, data: Partial) => {
+ const initialData = this.intakeForms[projectId];
try {
runInAction(() => {
- set(this.intakeForms, projectId, { ...this.intakeForms[projectId], is_disabled: isDisabled });
+ set(this.intakeForms, projectId, { ...this.intakeForms[projectId], ...data });
+ });
+ const result = await this.inboxIssueService.updatePublishForm(workspaceSlug, projectId, data);
+ runInAction(() => {
+ set(this.intakeForms, projectId, { ...this.intakeForms[projectId], anchor: result?.anchor });
});
- await this.inboxIssueService.updatePublishForm(workspaceSlug, projectId, isDisabled);
} catch {
console.error("Error fetching the publish forms");
runInAction(() => {
- set(this.intakeForms, projectId, { ...this.intakeForms[projectId], is_disabled: !isDisabled });
+ set(this.intakeForms, projectId, initialData);
});
}
};
-
regenerateIntakeForms = async (workspaceSlug: string, projectId: string) => {
try {
const form = await this.inboxIssueService.regeneratePublishForm(workspaceSlug, projectId);
From ea8583b2d46b3003543deb16521a1e4578a0e1cd Mon Sep 17 00:00:00 2001
From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Date: Tue, 5 Nov 2024 17:04:03 +0530
Subject: [PATCH 2/7] chore: code refactor (#5952)
* chore: code refactor
* chore: code refactor
---
web/ce/components/de-dupe/de-dupe-button.tsx | 15 ++++++++++++++
web/ce/components/de-dupe/index.ts | 1 +
.../inbox/modals/create-modal/create-root.tsx | 20 +++++++------------
.../components/issues/issue-modal/form.tsx | 20 +++++++------------
web/ee/components/de-dupe/index.ts | 1 +
5 files changed, 31 insertions(+), 26 deletions(-)
create mode 100644 web/ce/components/de-dupe/de-dupe-button.tsx
create mode 100644 web/ee/components/de-dupe/index.ts
diff --git a/web/ce/components/de-dupe/de-dupe-button.tsx b/web/ce/components/de-dupe/de-dupe-button.tsx
new file mode 100644
index 0000000000..eaa4e3b7c8
--- /dev/null
+++ b/web/ce/components/de-dupe/de-dupe-button.tsx
@@ -0,0 +1,15 @@
+"use client";
+import React, { FC } from "react";
+// local components
+
+type TDeDupeButtonRoot = {
+ workspaceSlug: string;
+ isDuplicateModalOpen: boolean;
+ handleOnClick: () => void;
+ label: string;
+};
+
+export const DeDupeButtonRoot: FC = (props) => {
+ const { workspaceSlug, isDuplicateModalOpen, label, handleOnClick } = props;
+ return <>>;
+};
diff --git a/web/ce/components/de-dupe/index.ts b/web/ce/components/de-dupe/index.ts
index 83622b978c..91856db18e 100644
--- a/web/ce/components/de-dupe/index.ts
+++ b/web/ce/components/de-dupe/index.ts
@@ -1,3 +1,4 @@
+export * from "./de-dupe-button";
export * from "./duplicate-modal";
export * from "./duplicate-popover";
export * from "./issue-block";
diff --git a/web/core/components/inbox/modals/create-modal/create-root.tsx b/web/core/components/inbox/modals/create-modal/create-root.tsx
index 4a318d1b2a..edd17060cd 100644
--- a/web/core/components/inbox/modals/create-modal/create-root.tsx
+++ b/web/core/components/inbox/modals/create-modal/create-root.tsx
@@ -22,7 +22,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
import useKeypress from "@/hooks/use-keypress";
import { usePlatformOS } from "@/hooks/use-platform-os";
// services
-import { DeDupeIssueButtonLabel, DuplicateModalRoot } from "@/plane-web/components/de-dupe";
+import { DeDupeButtonRoot, DuplicateModalRoot } from "@/plane-web/components/de-dupe";
import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
import { FileService } from "@/services/file.service";
@@ -210,18 +210,12 @@ export const InboxIssueCreateRoot: FC = observer((props)
Create intake issue
{duplicateIssues?.length > 0 && (
-
+ 1 ? "s" : ""} found!`}
+ handleOnClick={() => handleDuplicateIssueModal(!isDuplicateModalOpen)}
+ />
)}
diff --git a/web/core/components/issues/issue-modal/form.tsx b/web/core/components/issues/issue-modal/form.tsx
index f9dd9d06dd..b99b517495 100644
--- a/web/core/components/issues/issue-modal/form.tsx
+++ b/web/core/components/issues/issue-modal/form.tsx
@@ -31,7 +31,7 @@ import { useIssueDetail, useProject, useProjectState, useWorkspaceDraftIssues }
import { usePlatformOS } from "@/hooks/use-platform-os";
import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties";
// plane web components
-import { DeDupeIssueButtonLabel, DuplicateModalRoot } from "@/plane-web/components/de-dupe";
+import { DeDupeButtonRoot, DuplicateModalRoot } from "@/plane-web/components/de-dupe";
import { IssueAdditionalProperties, IssueTypeSelect } from "@/plane-web/components/issues/issue-modal";
import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
@@ -350,18 +350,12 @@ export const IssueFormRoot: FC = observer((props) => {
)}
{duplicateIssues.length > 0 && (
-
+ 1 ? "s" : ""} found!`}
+ handleOnClick={() => handleDuplicateIssueModal(!isDuplicateModalOpen)}
+ />
)}
{watch("parent_id") && selectedParentIssue && (
diff --git a/web/ee/components/de-dupe/index.ts b/web/ee/components/de-dupe/index.ts
new file mode 100644
index 0000000000..1c66dae21d
--- /dev/null
+++ b/web/ee/components/de-dupe/index.ts
@@ -0,0 +1 @@
+export * from "ce/components/de-dupe";
From bb311b750fc71895d7159d3f9cc19e2e021c2718 Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Date: Tue, 5 Nov 2024 17:45:53 +0530
Subject: [PATCH 3/7] fix: wrong token being passed in the read-only editor
(#5954)
* fix: wrong token
* chore: update useMemo dependencies
---
.../src/core/hooks/use-read-only-collaborative-editor.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts b/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
index 9fa73c3ecb..d408192297 100644
--- a/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
+++ b/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
@@ -32,7 +32,7 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
new HocuspocusProvider({
url: realtimeConfig.url,
name: id,
- token: user.id,
+ token: JSON.stringify(user),
parameters: realtimeConfig.queryParams,
onAuthenticationFailed: () => {
serverHandler?.onServerError?.();
@@ -47,7 +47,7 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
},
onSynced: () => setHasServerSynced(true),
}),
- [id, realtimeConfig, user.id]
+ [id, realtimeConfig, user]
);
// destroy and disconnect connection on unmount
useEffect(
From b4de055463e8328fcc5124a7c43a29ff2c553204 Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Date: Tue, 5 Nov 2024 17:46:34 +0530
Subject: [PATCH 4/7] [PULSE-42] feat: text alignment for all editors (#5847)
* feat: text alignment for editors
* fix: text alignment types
* fix: build errors
* fix: build error
* fix: toolbar movement post alignment selection
* fix: callout type
* fix: image node types
* chore: add ts error warning
---
packages/editor/package.json | 1 +
.../menus/bubble-menu/alignment-selector.tsx | 91 +++++++++++++
.../menus/bubble-menu/color-selector.tsx | 8 +-
.../menus/bubble-menu/node-selector.tsx | 6 +-
.../components/menus/bubble-menu/root.tsx | 18 ++-
.../src/core/components/menus/menu-items.ts | 98 +++++++-------
.../src/core/extensions/core-without-props.ts | 2 +
.../editor/src/core/extensions/extensions.tsx | 2 +
packages/editor/src/core/extensions/index.ts | 4 +-
.../core/extensions/read-only-extensions.tsx | 2 +
.../slash-commands/command-menu.tsx | 4 +-
.../core/extensions/slash-commands/root.tsx | 4 +-
.../editor/src/core/extensions/text-align.ts | 8 ++
.../src/core/helpers/editor-commands.ts | 6 +-
packages/editor/src/core/hooks/use-editor.ts | 16 +--
packages/editor/src/core/types/editor.ts | 77 ++++++++---
.../core/types/slash-commands-suggestion.ts | 30 +----
.../components/editor/lite-text-editor.tsx | 20 +--
space/core/components/editor/toolbar.tsx | 95 ++++++-------
.../peek-overview/comment/add-comment.tsx | 2 +-
space/core/constants/editor.ts | 126 +++++++++++++----
.../lite-text-editor/lite-text-editor.tsx | 23 ++--
.../editor/lite-text-editor/toolbar.tsx | 95 ++++++-------
.../pages/editor/header/color-dropdown.tsx | 12 +-
.../pages/editor/header/toolbar.tsx | 33 +++--
web/core/constants/editor.ts | 127 ++++++++++++++----
yarn.lock | 55 ++++----
27 files changed, 641 insertions(+), 324 deletions(-)
create mode 100644 packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx
create mode 100644 packages/editor/src/core/extensions/text-align.ts
diff --git a/packages/editor/package.json b/packages/editor/package.json
index c52c630c8c..067be0145d 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -48,6 +48,7 @@
"@tiptap/extension-placeholder": "^2.3.0",
"@tiptap/extension-task-item": "^2.1.13",
"@tiptap/extension-task-list": "^2.1.13",
+ "@tiptap/extension-text-align": "^2.8.0",
"@tiptap/extension-text-style": "^2.7.1",
"@tiptap/extension-underline": "^2.1.13",
"@tiptap/pm": "^2.1.13",
diff --git a/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx
new file mode 100644
index 0000000000..e3ccc6cf62
--- /dev/null
+++ b/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx
@@ -0,0 +1,91 @@
+import { Editor } from "@tiptap/core";
+import { AlignCenter, AlignLeft, AlignRight, LucideIcon } from "lucide-react";
+// components
+import { TextAlignItem } from "@/components/menus";
+// helpers
+import { cn } from "@/helpers/common";
+// types
+import { TEditorCommands } from "@/types";
+
+type Props = {
+ editor: Editor;
+ onClose: () => void;
+};
+
+export const TextAlignmentSelector: React.FC = (props) => {
+ const { editor, onClose } = props;
+
+ const menuItem = TextAlignItem(editor);
+
+ const textAlignmentOptions: {
+ itemKey: TEditorCommands;
+ renderKey: string;
+ icon: LucideIcon;
+ command: () => void;
+ isActive: () => boolean;
+ }[] = [
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-left",
+ icon: AlignLeft,
+ command: () =>
+ menuItem.command({
+ alignment: "left",
+ }),
+ isActive: () =>
+ menuItem.isActive({
+ alignment: "left",
+ }),
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-center",
+ icon: AlignCenter,
+ command: () =>
+ menuItem.command({
+ alignment: "center",
+ }),
+ isActive: () =>
+ menuItem.isActive({
+ alignment: "center",
+ }),
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-right",
+ icon: AlignRight,
+ command: () =>
+ menuItem.command({
+ alignment: "right",
+ }),
+ isActive: () =>
+ menuItem.isActive({
+ alignment: "right",
+ }),
+ },
+ ];
+
+ return (
+
+ {textAlignmentOptions.map((item) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx
index b48070775e..bc7f5a56f1 100644
--- a/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx
+++ b/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx
@@ -16,8 +16,8 @@ type Props = {
export const BubbleMenuColorSelector: FC = (props) => {
const { editor, isOpen, setIsOpen } = props;
- const activeTextColor = COLORS_LIST.find((c) => TextColorItem(editor).isActive(c.key));
- const activeBackgroundColor = COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive(c.key));
+ const activeTextColor = COLORS_LIST.find((c) => TextColorItem(editor).isActive({ color: c.key }));
+ const activeBackgroundColor = COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive({ color: c.key }));
return (
@@ -64,7 +64,7 @@ export const BubbleMenuColorSelector: FC
= (props) => {
style={{
backgroundColor: color.textColor,
}}
- onClick={() => TextColorItem(editor).command(color.key)}
+ onClick={() => TextColorItem(editor).command({ color: color.key })}
/>
))}
- {items.map((item) => (
+ {basicFormattingOptions.map((item) => (
= (props: any) => {
))}
+ {
+ const editor = props.editor as Editor;
+ if (!editor) return;
+ const pos = editor.state.selection.to;
+ editor.commands.setTextSelection(pos ?? 0);
+ }}
+ />
>
)}
diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts
index 0ece455ed2..5e987fca6d 100644
--- a/packages/editor/src/core/components/menus/menu-items.ts
+++ b/packages/editor/src/core/components/menus/menu-items.ts
@@ -1,4 +1,3 @@
-import { Selection } from "@tiptap/pm/state";
import { Editor } from "@tiptap/react";
import {
BoldIcon,
@@ -22,6 +21,7 @@ import {
LucideIcon,
MinusSquare,
Palette,
+ AlignCenter,
} from "lucide-react";
// helpers
import {
@@ -29,6 +29,7 @@ import {
insertImage,
insertTableCommand,
setText,
+ setTextAlign,
toggleBackgroundColor,
toggleBlockquote,
toggleBold,
@@ -48,24 +49,20 @@ import {
toggleUnderline,
} from "@/helpers/editor-commands";
// types
-import { TColorEditorCommands, TNonColorEditorCommands } from "@/types";
+import { TCommandWithProps, TEditorCommands } from "@/types";
-export type EditorMenuItem = {
+type isActiveFunction = (params?: TCommandWithProps) => boolean;
+type commandFunction = (params?: TCommandWithProps) => void;
+
+export type EditorMenuItem = {
+ key: T;
name: string;
- command: (...args: any) => void;
+ command: commandFunction;
icon: LucideIcon;
-} & (
- | {
- key: TNonColorEditorCommands;
- isActive: () => boolean;
- }
- | {
- key: TColorEditorCommands;
- isActive: (color: string | undefined) => boolean;
- }
-);
+ isActive: isActiveFunction;
+};
-export const TextItem = (editor: Editor): EditorMenuItem => ({
+export const TextItem = (editor: Editor): EditorMenuItem<"text"> => ({
key: "text",
name: "Text",
isActive: () => editor.isActive("paragraph"),
@@ -73,7 +70,7 @@ export const TextItem = (editor: Editor): EditorMenuItem => ({
icon: CaseSensitive,
});
-export const HeadingOneItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingOneItem = (editor: Editor): EditorMenuItem<"h1"> => ({
key: "h1",
name: "Heading 1",
isActive: () => editor.isActive("heading", { level: 1 }),
@@ -81,7 +78,7 @@ export const HeadingOneItem = (editor: Editor): EditorMenuItem => ({
icon: Heading1,
});
-export const HeadingTwoItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingTwoItem = (editor: Editor): EditorMenuItem<"h2"> => ({
key: "h2",
name: "Heading 2",
isActive: () => editor.isActive("heading", { level: 2 }),
@@ -89,7 +86,7 @@ export const HeadingTwoItem = (editor: Editor): EditorMenuItem => ({
icon: Heading2,
});
-export const HeadingThreeItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingThreeItem = (editor: Editor): EditorMenuItem<"h3"> => ({
key: "h3",
name: "Heading 3",
isActive: () => editor.isActive("heading", { level: 3 }),
@@ -97,7 +94,7 @@ export const HeadingThreeItem = (editor: Editor): EditorMenuItem => ({
icon: Heading3,
});
-export const HeadingFourItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingFourItem = (editor: Editor): EditorMenuItem<"h4"> => ({
key: "h4",
name: "Heading 4",
isActive: () => editor.isActive("heading", { level: 4 }),
@@ -105,7 +102,7 @@ export const HeadingFourItem = (editor: Editor): EditorMenuItem => ({
icon: Heading4,
});
-export const HeadingFiveItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingFiveItem = (editor: Editor): EditorMenuItem<"h5"> => ({
key: "h5",
name: "Heading 5",
isActive: () => editor.isActive("heading", { level: 5 }),
@@ -113,7 +110,7 @@ export const HeadingFiveItem = (editor: Editor): EditorMenuItem => ({
icon: Heading5,
});
-export const HeadingSixItem = (editor: Editor): EditorMenuItem => ({
+export const HeadingSixItem = (editor: Editor): EditorMenuItem<"h6"> => ({
key: "h6",
name: "Heading 6",
isActive: () => editor.isActive("heading", { level: 6 }),
@@ -121,7 +118,7 @@ export const HeadingSixItem = (editor: Editor): EditorMenuItem => ({
icon: Heading6,
});
-export const BoldItem = (editor: Editor): EditorMenuItem => ({
+export const BoldItem = (editor: Editor): EditorMenuItem<"bold"> => ({
key: "bold",
name: "Bold",
isActive: () => editor?.isActive("bold"),
@@ -129,7 +126,7 @@ export const BoldItem = (editor: Editor): EditorMenuItem => ({
icon: BoldIcon,
});
-export const ItalicItem = (editor: Editor): EditorMenuItem => ({
+export const ItalicItem = (editor: Editor): EditorMenuItem<"italic"> => ({
key: "italic",
name: "Italic",
isActive: () => editor?.isActive("italic"),
@@ -137,7 +134,7 @@ export const ItalicItem = (editor: Editor): EditorMenuItem => ({
icon: ItalicIcon,
});
-export const UnderLineItem = (editor: Editor): EditorMenuItem => ({
+export const UnderLineItem = (editor: Editor): EditorMenuItem<"underline"> => ({
key: "underline",
name: "Underline",
isActive: () => editor?.isActive("underline"),
@@ -145,7 +142,7 @@ export const UnderLineItem = (editor: Editor): EditorMenuItem => ({
icon: UnderlineIcon,
});
-export const StrikeThroughItem = (editor: Editor): EditorMenuItem => ({
+export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strikethrough"> => ({
key: "strikethrough",
name: "Strikethrough",
isActive: () => editor?.isActive("strike"),
@@ -153,7 +150,7 @@ export const StrikeThroughItem = (editor: Editor): EditorMenuItem => ({
icon: StrikethroughIcon,
});
-export const BulletListItem = (editor: Editor): EditorMenuItem => ({
+export const BulletListItem = (editor: Editor): EditorMenuItem<"bulleted-list"> => ({
key: "bulleted-list",
name: "Bulleted list",
isActive: () => editor?.isActive("bulletList"),
@@ -161,7 +158,7 @@ export const BulletListItem = (editor: Editor): EditorMenuItem => ({
icon: ListIcon,
});
-export const NumberedListItem = (editor: Editor): EditorMenuItem => ({
+export const NumberedListItem = (editor: Editor): EditorMenuItem<"numbered-list"> => ({
key: "numbered-list",
name: "Numbered list",
isActive: () => editor?.isActive("orderedList"),
@@ -169,7 +166,7 @@ export const NumberedListItem = (editor: Editor): EditorMenuItem => ({
icon: ListOrderedIcon,
});
-export const TodoListItem = (editor: Editor): EditorMenuItem => ({
+export const TodoListItem = (editor: Editor): EditorMenuItem<"to-do-list"> => ({
key: "to-do-list",
name: "To-do list",
isActive: () => editor.isActive("taskItem"),
@@ -177,7 +174,7 @@ export const TodoListItem = (editor: Editor): EditorMenuItem => ({
icon: CheckSquare,
});
-export const QuoteItem = (editor: Editor): EditorMenuItem => ({
+export const QuoteItem = (editor: Editor): EditorMenuItem<"quote"> => ({
key: "quote",
name: "Quote",
isActive: () => editor?.isActive("blockquote"),
@@ -185,7 +182,7 @@ export const QuoteItem = (editor: Editor): EditorMenuItem => ({
icon: TextQuote,
});
-export const CodeItem = (editor: Editor): EditorMenuItem => ({
+export const CodeItem = (editor: Editor): EditorMenuItem<"code"> => ({
key: "code",
name: "Code",
isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"),
@@ -193,7 +190,7 @@ export const CodeItem = (editor: Editor): EditorMenuItem => ({
icon: CodeIcon,
});
-export const TableItem = (editor: Editor): EditorMenuItem => ({
+export const TableItem = (editor: Editor): EditorMenuItem<"table"> => ({
key: "table",
name: "Table",
isActive: () => editor?.isActive("table"),
@@ -201,14 +198,14 @@ export const TableItem = (editor: Editor): EditorMenuItem => ({
icon: TableIcon,
});
-export const ImageItem = (editor: Editor) =>
- ({
- key: "image",
- name: "Image",
- isActive: () => editor?.isActive("image") || editor?.isActive("imageComponent"),
- command: (savedSelection: Selection | null) => insertImage({ editor, event: "insert", pos: savedSelection?.from }),
- icon: ImageIcon,
- }) as const;
+export const ImageItem = (editor: Editor): EditorMenuItem<"image"> => ({
+ key: "image",
+ name: "Image",
+ isActive: () => editor?.isActive("image") || editor?.isActive("imageComponent"),
+ command: ({ savedSelection }) =>
+ insertImage({ editor, event: "insert", pos: savedSelection?.from ?? editor.state.selection.from }),
+ icon: ImageIcon,
+});
export const HorizontalRuleItem = (editor: Editor) =>
({
@@ -219,23 +216,31 @@ export const HorizontalRuleItem = (editor: Editor) =>
icon: MinusSquare,
}) as const;
-export const TextColorItem = (editor: Editor): EditorMenuItem => ({
+export const TextColorItem = (editor: Editor): EditorMenuItem<"text-color"> => ({
key: "text-color",
name: "Color",
- isActive: (color) => editor.isActive("customColor", { color }),
- command: (color: string) => toggleTextColor(color, editor),
+ isActive: ({ color }) => editor.isActive("customColor", { color }),
+ command: ({ color }) => toggleTextColor(color, editor),
icon: Palette,
});
-export const BackgroundColorItem = (editor: Editor): EditorMenuItem => ({
+export const BackgroundColorItem = (editor: Editor): EditorMenuItem<"background-color"> => ({
key: "background-color",
name: "Background color",
- isActive: (color) => editor.isActive("customColor", { backgroundColor: color }),
- command: (color: string) => toggleBackgroundColor(color, editor),
+ isActive: ({ color }) => editor.isActive("customColor", { backgroundColor: color }),
+ command: ({ color }) => toggleBackgroundColor(color, editor),
icon: Palette,
});
-export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem[] => {
+export const TextAlignItem = (editor: Editor): EditorMenuItem<"text-align"> => ({
+ key: "text-align",
+ name: "Text align",
+ isActive: ({ alignment }) => editor.isActive({ textAlign: alignment }),
+ command: ({ alignment }) => setTextAlign(alignment, editor),
+ icon: AlignCenter,
+});
+
+export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem[] => {
if (!editor) return [];
return [
@@ -260,5 +265,6 @@ export const getEditorMenuItems = (editor: Editor | null): EditorMenuItem[] => {
HorizontalRuleItem(editor),
TextColorItem(editor),
BackgroundColorItem(editor),
+ TextAlignItem(editor),
];
};
diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts
index 39a243f88a..abceea4ff5 100644
--- a/packages/editor/src/core/extensions/core-without-props.ts
+++ b/packages/editor/src/core/extensions/core-without-props.ts
@@ -16,6 +16,7 @@ import { IssueWidgetWithoutProps } from "./issue-embed/issue-embed-without-props
import { CustomMentionWithoutProps } from "./mentions/mentions-without-props";
import { CustomQuoteExtension } from "./quote";
import { TableHeader, TableCell, TableRow, Table } from "./table";
+import { CustomTextAlignExtension } from "./text-align";
import { CustomCalloutExtensionConfig } from "./callout/extension-config";
import { CustomColorExtension } from "./custom-color";
@@ -85,6 +86,7 @@ export const CoreEditorExtensionsWithoutProps = [
TableCell,
TableRow,
CustomMentionWithoutProps(),
+ CustomTextAlignExtension,
CustomCalloutExtensionConfig,
CustomColorExtension,
];
diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx
index 24a372b4aa..3907e3b9d2 100644
--- a/packages/editor/src/core/extensions/extensions.tsx
+++ b/packages/editor/src/core/extensions/extensions.tsx
@@ -19,6 +19,7 @@ import {
CustomLinkExtension,
CustomMention,
CustomQuoteExtension,
+ CustomTextAlignExtension,
CustomTypographyExtension,
DropHandlerExtension,
ImageExtension,
@@ -158,6 +159,7 @@ export const CoreEditorExtensions = (args: TArguments) => {
includeChildren: true,
}),
CharacterCount,
+ CustomTextAlignExtension,
CustomCalloutExtension,
CustomColorExtension,
];
diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts
index dbd529d8c1..d1fa0ce6db 100644
--- a/packages/editor/src/core/extensions/index.ts
+++ b/packages/editor/src/core/extensions/index.ts
@@ -16,10 +16,10 @@ export * from "./custom-color";
export * from "./drop";
export * from "./enter-key-extension";
export * from "./extensions";
+export * from "./headers";
export * from "./horizontal-rule";
export * from "./keymap";
export * from "./quote";
export * from "./read-only-extensions";
export * from "./side-menu";
-export * from "./slash-commands";
-export * from "./headers";
+export * from "./text-align";
diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx
index 096cefbae6..31ae1d90a1 100644
--- a/packages/editor/src/core/extensions/read-only-extensions.tsx
+++ b/packages/editor/src/core/extensions/read-only-extensions.tsx
@@ -21,6 +21,7 @@ import {
CustomMention,
HeadingListExtension,
CustomReadOnlyImageExtension,
+ CustomTextAlignExtension,
CustomCalloutReadOnlyExtension,
CustomColorExtension,
} from "@/extensions";
@@ -125,6 +126,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => {
CharacterCount,
CustomColorExtension,
HeadingListExtension,
+ CustomTextAlignExtension,
CustomCalloutReadOnlyExtension,
];
};
diff --git a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx
index c6363bc51c..d6148b69ae 100644
--- a/packages/editor/src/core/extensions/slash-commands/command-menu.tsx
+++ b/packages/editor/src/core/extensions/slash-commands/command-menu.tsx
@@ -3,12 +3,12 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react
import { TSlashCommandSection } from "./command-items-list";
import { CommandMenuItem } from "./command-menu-item";
-type Props = {
+export type SlashCommandsMenuProps = {
items: TSlashCommandSection[];
command: any;
};
-export const SlashCommandsMenu = (props: Props) => {
+export const SlashCommandsMenu = (props: SlashCommandsMenuProps) => {
const { items: sections, command } = props;
// states
const [selectedIndex, setSelectedIndex] = useState({
diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx
index df70820dcf..a99cbc5f90 100644
--- a/packages/editor/src/core/extensions/slash-commands/root.tsx
+++ b/packages/editor/src/core/extensions/slash-commands/root.tsx
@@ -6,7 +6,7 @@ import tippy from "tippy.js";
import { ISlashCommandItem } from "@/types";
// components
import { getSlashCommandFilteredSections } from "./command-items-list";
-import { SlashCommandsMenu } from "./command-menu";
+import { SlashCommandsMenu, SlashCommandsMenuProps } from "./command-menu";
export type SlashCommandOptions = {
suggestion: Omit;
@@ -55,7 +55,7 @@ interface CommandListInstance {
}
const renderItems = () => {
- let component: ReactRenderer | null = null;
+ let component: ReactRenderer | null = null;
let popup: any | null = null;
return {
onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
diff --git a/packages/editor/src/core/extensions/text-align.ts b/packages/editor/src/core/extensions/text-align.ts
new file mode 100644
index 0000000000..bfe62f6c04
--- /dev/null
+++ b/packages/editor/src/core/extensions/text-align.ts
@@ -0,0 +1,8 @@
+import TextAlign from "@tiptap/extension-text-align";
+
+export type TTextAlign = "left" | "center" | "right";
+
+export const CustomTextAlignExtension = TextAlign.configure({
+ alignments: ["left", "center", "right"],
+ types: ["heading", "paragraph"],
+});
diff --git a/packages/editor/src/core/helpers/editor-commands.ts b/packages/editor/src/core/helpers/editor-commands.ts
index efa19e3876..ec593d5367 100644
--- a/packages/editor/src/core/helpers/editor-commands.ts
+++ b/packages/editor/src/core/helpers/editor-commands.ts
@@ -181,10 +181,14 @@ export const toggleBackgroundColor = (color: string | undefined, editor: Editor,
}
};
+export const setTextAlign = (alignment: string, editor: Editor) => {
+ editor.chain().focus().setTextAlign(alignment).run();
+};
+
export const insertHorizontalRule = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).setHorizontalRule().run();
else editor.chain().focus().setHorizontalRule().run();
-}
+};
export const insertCallout = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).insertCallout().run();
else editor.chain().focus().insertCallout().run();
diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts
index 67d7799660..eef72797ce 100644
--- a/packages/editor/src/core/hooks/use-editor.ts
+++ b/packages/editor/src/core/hooks/use-editor.ts
@@ -6,7 +6,7 @@ import { EditorProps } from "@tiptap/pm/view";
import { useEditor as useTiptapEditor, Editor } from "@tiptap/react";
import * as Y from "yjs";
// components
-import { getEditorMenuItems } from "@/components/menus";
+import { EditorMenuItem, getEditorMenuItems } from "@/components/menus";
// extensions
import { CoreEditorExtensions } from "@/extensions";
// helpers
@@ -155,11 +155,11 @@ export const useEditor = (props: CustomEditorProps) => {
const item = getEditorMenuItem(itemKey);
if (item) {
if (item.key === "image") {
- item.command(savedSelectionRef.current);
- } else if (itemKey === "text-color" || itemKey === "background-color") {
- item.command(props.color);
+ (item as EditorMenuItem<"image">).command({
+ savedSelection: savedSelectionRef.current,
+ });
} else {
- item.command();
+ item.command(props);
}
} else {
console.warn(`No command found for item: ${itemKey}`);
@@ -173,11 +173,7 @@ export const useEditor = (props: CustomEditorProps) => {
const item = getEditorMenuItem(itemKey);
if (!item) return false;
- if (itemKey === "text-color" || itemKey === "background-color") {
- return item.isActive(props.color);
- } else {
- return item.isActive("");
- }
+ return item.isActive(props);
},
onHeadingChange: (callback: (headings: IMarking[]) => void) => {
// Subscribe to update event emitted from headers extension
diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts
index 97f22e7723..53aae1f265 100644
--- a/packages/editor/src/core/types/editor.ts
+++ b/packages/editor/src/core/types/editor.ts
@@ -1,4 +1,5 @@
import { JSONContent } from "@tiptap/core";
+import { Selection } from "@tiptap/pm/state";
// helpers
import { IMarking } from "@/helpers/scroll-to-node";
// types
@@ -6,14 +7,64 @@ import {
IMentionHighlight,
IMentionSuggestion,
TAIHandler,
- TColorEditorCommands,
TDisplayConfig,
TEmbedConfig,
TExtensions,
TFileHandler,
- TNonColorEditorCommands,
TServerHandler,
} from "@/types";
+import { TTextAlign } from "@/extensions";
+
+export type TEditorCommands =
+ | "text"
+ | "h1"
+ | "h2"
+ | "h3"
+ | "h4"
+ | "h5"
+ | "h6"
+ | "bold"
+ | "italic"
+ | "underline"
+ | "strikethrough"
+ | "bulleted-list"
+ | "numbered-list"
+ | "to-do-list"
+ | "quote"
+ | "code"
+ | "table"
+ | "image"
+ | "divider"
+ | "issue-embed"
+ | "text-color"
+ | "background-color"
+ | "text-align"
+ | "callout";
+
+export type TCommandExtraProps = {
+ image: {
+ savedSelection: Selection | null;
+ };
+ "text-color": {
+ color: string | undefined;
+ };
+ "background-color": {
+ color: string | undefined;
+ };
+ "text-align": {
+ alignment: TTextAlign;
+ };
+};
+
+// Create a utility type that maps a command to its extra props or an empty object if none are defined
+export type TCommandWithProps = T extends keyof TCommandExtraProps
+ ? TCommandExtraProps[T] // If the command has extra props, include them
+ : object; // Otherwise, just return the command type with no extra props
+
+type TCommandWithPropsWithItemKey = T extends keyof TCommandExtraProps
+ ? { itemKey: T } & TCommandExtraProps[T]
+ : { itemKey: T };
+
// editor refs
export type EditorReadOnlyRefApi = {
getMarkDown: () => string;
@@ -39,26 +90,8 @@ export interface EditorRefApi extends EditorReadOnlyRefApi {
scrollToNodeViaDOMCoordinates: (behavior?: ScrollBehavior, position?: number) => void;
getCurrentCursorPosition: () => number | undefined;
setEditorValueAtCursorPosition: (content: string) => void;
- executeMenuItemCommand: (
- props:
- | {
- itemKey: TNonColorEditorCommands;
- }
- | {
- itemKey: TColorEditorCommands;
- color: string | undefined;
- }
- ) => void;
- isMenuItemActive: (
- props:
- | {
- itemKey: TNonColorEditorCommands;
- }
- | {
- itemKey: TColorEditorCommands;
- color: string | undefined;
- }
- ) => boolean;
+ executeMenuItemCommand: (props: TCommandWithPropsWithItemKey) => void;
+ isMenuItemActive: (props: TCommandWithPropsWithItemKey) => boolean;
onStateChange: (callback: () => void) => () => void;
setFocusAtPosition: (position: number) => void;
isEditorReadyToDiscard: () => boolean;
diff --git a/packages/editor/src/core/types/slash-commands-suggestion.ts b/packages/editor/src/core/types/slash-commands-suggestion.ts
index 122231111e..91c93203af 100644
--- a/packages/editor/src/core/types/slash-commands-suggestion.ts
+++ b/packages/editor/src/core/types/slash-commands-suggestion.ts
@@ -1,33 +1,7 @@
import { CSSProperties } from "react";
import { Editor, Range } from "@tiptap/core";
-
-export type TEditorCommands =
- | "text"
- | "h1"
- | "h2"
- | "h3"
- | "h4"
- | "h5"
- | "h6"
- | "bold"
- | "italic"
- | "underline"
- | "strikethrough"
- | "bulleted-list"
- | "numbered-list"
- | "to-do-list"
- | "quote"
- | "code"
- | "table"
- | "image"
- | "divider"
- | "issue-embed"
- | "text-color"
- | "background-color"
- | "callout";
-
-export type TColorEditorCommands = Extract;
-export type TNonColorEditorCommands = Exclude;
+// types
+import { TEditorCommands } from "@/types";
export type CommandProps = {
editor: Editor;
diff --git a/space/core/components/editor/lite-text-editor.tsx b/space/core/components/editor/lite-text-editor.tsx
index 4cd6d82e87..0e3f34293b 100644
--- a/space/core/components/editor/lite-text-editor.tsx
+++ b/space/core/components/editor/lite-text-editor.tsx
@@ -1,6 +1,6 @@
import React from "react";
// editor
-import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef, TNonColorEditorCommands } from "@plane/editor";
+import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
// components
import { IssueCommentToolbar } from "@/components/editor";
// helpers
@@ -30,11 +30,12 @@ export const LiteTextEditor = React.forwardRef(ref: React.ForwardedRef): ref is React.MutableRefObject {
return !!ref && typeof ref === "object" && "current" in ref;
}
+ // derived values
const isEmpty = isCommentEmpty(props.initialValue);
+ const editorRef = isMutableRefObject(ref) ? ref.current : null;
return (
@@ -54,18 +55,19 @@ export const LiteTextEditor = React.forwardRef
{
- if (isMutableRefObject(ref)) {
- ref.current?.executeMenuItemCommand({
- itemKey: key as TNonColorEditorCommands,
- });
- }
+ executeCommand={(item) => {
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
+ editorRef?.executeMenuItemCommand({
+ itemKey: item.itemKey,
+ ...item.extraProps,
+ });
}}
isSubmitting={isSubmitting}
showSubmitButton={showSubmitButton}
handleSubmit={(e) => rest.onEnterKeyPress?.(e)}
isCommentEmpty={isEmpty}
- editorRef={isMutableRefObject(ref) ? ref : null}
+ editorRef={editorRef}
/>
);
diff --git a/space/core/components/editor/toolbar.tsx b/space/core/components/editor/toolbar.tsx
index beccc8cb76..4593aaf653 100644
--- a/space/core/components/editor/toolbar.tsx
+++ b/space/core/components/editor/toolbar.tsx
@@ -2,21 +2,21 @@
import React, { useEffect, useState, useCallback } from "react";
// editor
-import { EditorRefApi, TEditorCommands, TNonColorEditorCommands } from "@plane/editor";
+import { EditorRefApi } from "@plane/editor";
// ui
import { Button, Tooltip } from "@plane/ui";
// constants
-import { TOOLBAR_ITEMS } from "@/constants/editor";
+import { TOOLBAR_ITEMS, ToolbarMenuItem } from "@/constants/editor";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
- executeCommand: (commandKey: TEditorCommands) => void;
+ executeCommand: (item: ToolbarMenuItem) => void;
handleSubmit: (event: React.MouseEvent) => void;
isCommentEmpty: boolean;
isSubmitting: boolean;
showSubmitButton: boolean;
- editorRef: React.MutableRefObject | null;
+ editorRef: EditorRefApi | null;
};
const toolbarItems = TOOLBAR_ITEMS.lite;
@@ -28,24 +28,25 @@ export const IssueCommentToolbar: React.FC = (props) => {
// Function to update active states
const updateActiveStates = useCallback(() => {
- if (editorRef?.current) {
- const newActiveStates: Record = {};
- Object.values(toolbarItems)
- .flat()
- .forEach((item) => {
- // Assert that editorRef.current is not null
- newActiveStates[item.key] = (editorRef.current as EditorRefApi).isMenuItemActive({
- itemKey: item.key as TNonColorEditorCommands,
- });
+ if (!editorRef) return;
+ const newActiveStates: Record = {};
+ Object.values(toolbarItems)
+ .flat()
+ .forEach((item) => {
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
+ newActiveStates[item.renderKey] = editorRef.isMenuItemActive({
+ itemKey: item.itemKey,
+ ...item.extraProps,
});
- setActiveStates(newActiveStates);
- }
+ });
+ setActiveStates(newActiveStates);
}, [editorRef]);
// useEffect to call updateActiveStates when isActive prop changes
useEffect(() => {
- if (!editorRef?.current) return;
- const unsubscribe = editorRef.current.onStateChange(updateActiveStates);
+ if (!editorRef) return;
+ const unsubscribe = editorRef.onStateChange(updateActiveStates);
updateActiveStates();
return () => unsubscribe();
}, [editorRef, updateActiveStates]);
@@ -61,35 +62,39 @@ export const IssueCommentToolbar: React.FC = (props) => {
"pl-0": index === 0,
})}
>
- {toolbarItems[key].map((item) => (
-
- {item.name}
- {item.shortcut && {item.shortcut.join(" + ")}}
-
- }
- >
- executeCommand(item.key)}
- className={cn(
- "grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-400 hover:bg-custom-background-80",
- {
- "bg-custom-background-80 text-custom-text-100": activeStates[item.key],
- }
- )}
+ {toolbarItems[key].map((item) => {
+ const isItemActive = activeStates[item.renderKey];
+
+ return (
+
+ {item.name}
+ {item.shortcut && {item.shortcut.join(" + ")}}
+
+ }
>
-
-
-
- ))}
+ executeCommand(item)}
+ className={cn(
+ "grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-400 hover:bg-custom-background-80",
+ {
+ "bg-custom-background-80 text-custom-text-100": isItemActive,
+ }
+ )}
+ >
+
+
+
+ );
+ })}
))}
diff --git a/space/core/components/issues/peek-overview/comment/add-comment.tsx b/space/core/components/issues/peek-overview/comment/add-comment.tsx
index f6c8a452b5..3623f39864 100644
--- a/space/core/components/issues/peek-overview/comment/add-comment.tsx
+++ b/space/core/components/issues/peek-overview/comment/add-comment.tsx
@@ -91,7 +91,7 @@ export const AddComment: React.FC = observer((props) => {
}
onChange={(comment_json, comment_html) => onChange(comment_html)}
isSubmitting={isSubmitting}
- placeholder="Add Comment..."
+ placeholder="Add comment..."
uploadFile={async (file) => {
const { asset_id } = await uploadCommentAsset(file, anchor);
setUploadAssetIds((prev) => [...prev, asset_id]);
diff --git a/space/core/constants/editor.ts b/space/core/constants/editor.ts
index 2c6ac2bbb7..6089c56046 100644
--- a/space/core/constants/editor.ts
+++ b/space/core/constants/editor.ts
@@ -1,5 +1,9 @@
import {
+ AlignCenter,
+ AlignLeft,
+ AlignRight,
Bold,
+ CaseSensitive,
Code2,
Heading1,
Heading2,
@@ -19,30 +23,99 @@ import {
Underline,
} from "lucide-react";
// editor
-import { TEditorCommands } from "@plane/editor";
+import { TCommandExtraProps, TEditorCommands } from "@plane/editor";
type TEditorTypes = "lite" | "document";
-export type ToolbarMenuItem = {
- key: TEditorCommands;
+// Utility type to enforce the necessary extra props or make extraProps optional
+type ExtraPropsForCommand = T extends keyof TCommandExtraProps
+ ? TCommandExtraProps[T]
+ : object; // Default to empty object for commands without extra props
+
+export type ToolbarMenuItem = {
+ itemKey: T;
+ renderKey: string;
name: string;
icon: LucideIcon;
shortcut?: string[];
editors: TEditorTypes[];
+ extraProps?: ExtraPropsForCommand;
};
-export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
- { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
- { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
- { key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
- { key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
- { key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
- { key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
- { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] },
- { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] },
- { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] },
+export const TYPOGRAPHY_ITEMS: ToolbarMenuItem<"text" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6">[] = [
+ { itemKey: "text", renderKey: "text", name: "Text", icon: CaseSensitive, editors: ["document"] },
+ { itemKey: "h1", renderKey: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
+ { itemKey: "h2", renderKey: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
+ { itemKey: "h3", renderKey: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
+ { itemKey: "h4", renderKey: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
+ { itemKey: "h5", renderKey: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
+ { itemKey: "h6", renderKey: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
+];
+
+export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [
{
- key: "strikethrough",
+ itemKey: "text-align",
+ renderKey: "text-align-left",
+ name: "Left align",
+ icon: AlignLeft,
+ shortcut: ["Cmd", "Shift", "L"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "left",
+ },
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-center",
+ name: "Center align",
+ icon: AlignCenter,
+ shortcut: ["Cmd", "Shift", "E"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "center",
+ },
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-right",
+ name: "Right align",
+ icon: AlignRight,
+ shortcut: ["Cmd", "Shift", "R"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "right",
+ },
+ },
+];
+
+const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [
+ {
+ itemKey: "bold",
+ renderKey: "bold",
+ name: "Bold",
+ icon: Bold,
+ shortcut: ["Cmd", "B"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "italic",
+ renderKey: "italic",
+ name: "Italic",
+ icon: Italic,
+ shortcut: ["Cmd", "I"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "underline",
+ renderKey: "underline",
+ name: "Underline",
+ icon: Underline,
+ shortcut: ["Cmd", "U"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "strikethrough",
+ renderKey: "strikethrough",
name: "Strikethrough",
icon: Strikethrough,
shortcut: ["Cmd", "Shift", "S"],
@@ -50,23 +123,26 @@ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
},
];
-export const LIST_ITEMS: ToolbarMenuItem[] = [
+const LIST_ITEMS: ToolbarMenuItem<"bulleted-list" | "numbered-list" | "to-do-list">[] = [
{
- key: "bulleted-list",
+ itemKey: "bulleted-list",
+ renderKey: "bulleted-list",
name: "Bulleted list",
icon: List,
shortcut: ["Cmd", "Shift", "7"],
editors: ["lite", "document"],
},
{
- key: "numbered-list",
+ itemKey: "numbered-list",
+ renderKey: "numbered-list",
name: "Numbered list",
icon: ListOrdered,
shortcut: ["Cmd", "Shift", "8"],
editors: ["lite", "document"],
},
{
- key: "to-do-list",
+ itemKey: "to-do-list",
+ renderKey: "to-do-list",
name: "To-do list",
icon: ListTodo,
shortcut: ["Cmd", "Shift", "9"],
@@ -74,14 +150,14 @@ export const LIST_ITEMS: ToolbarMenuItem[] = [
},
];
-export const USER_ACTION_ITEMS: ToolbarMenuItem[] = [
- { key: "quote", name: "Quote", icon: TextQuote, editors: ["lite", "document"] },
- { key: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
+export const USER_ACTION_ITEMS: ToolbarMenuItem<"quote" | "code">[] = [
+ { itemKey: "quote", renderKey: "quote", name: "Quote", icon: TextQuote, editors: ["lite", "document"] },
+ { itemKey: "code", renderKey: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
];
-export const COMPLEX_ITEMS: ToolbarMenuItem[] = [
- { key: "table", name: "Table", icon: Table, editors: ["document"] },
- { key: "image", name: "Image", icon: Image, editors: ["lite", "document"] },
+export const COMPLEX_ITEMS: ToolbarMenuItem<"table" | "image">[] = [
+ { itemKey: "table", renderKey: "table", name: "Table", icon: Table, editors: ["document"] },
+ { itemKey: "image", renderKey: "image", name: "Image", icon: Image, editors: ["lite", "document"] },
];
export const TOOLBAR_ITEMS: {
@@ -91,12 +167,14 @@ export const TOOLBAR_ITEMS: {
} = {
lite: {
basic: BASIC_MARK_ITEMS.filter((item) => item.editors.includes("lite")),
+ alignment: TEXT_ALIGNMENT_ITEMS.filter((item) => item.editors.includes("lite")),
list: LIST_ITEMS.filter((item) => item.editors.includes("lite")),
userAction: USER_ACTION_ITEMS.filter((item) => item.editors.includes("lite")),
complex: COMPLEX_ITEMS.filter((item) => item.editors.includes("lite")),
},
document: {
basic: BASIC_MARK_ITEMS.filter((item) => item.editors.includes("document")),
+ alignment: TEXT_ALIGNMENT_ITEMS.filter((item) => item.editors.includes("document")),
list: LIST_ITEMS.filter((item) => item.editors.includes("document")),
userAction: USER_ACTION_ITEMS.filter((item) => item.editors.includes("document")),
complex: COMPLEX_ITEMS.filter((item) => item.editors.includes("document")),
diff --git a/web/core/components/editor/lite-text-editor/lite-text-editor.tsx b/web/core/components/editor/lite-text-editor/lite-text-editor.tsx
index 3e64e83a33..0822f1a97d 100644
--- a/web/core/components/editor/lite-text-editor/lite-text-editor.tsx
+++ b/web/core/components/editor/lite-text-editor/lite-text-editor.tsx
@@ -1,6 +1,6 @@
import React from "react";
// editor
-import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef, TNonColorEditorCommands } from "@plane/editor";
+import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
// types
import { IUserLite } from "@plane/types";
// components
@@ -61,12 +61,12 @@ export const LiteTextEditor = React.forwardRef(ref: React.ForwardedRef): ref is React.MutableRefObject {
return !!ref && typeof ref === "object" && "current" in ref;
}
+ // derived values
+ const isEmpty = isCommentEmpty(props.initialValue);
+ const editorRef = isMutableRefObject(ref) ? ref.current : null;
return (
@@ -89,19 +89,20 @@ export const LiteTextEditor = React.forwardRef
{
- if (isMutableRefObject(ref)) {
- ref.current?.executeMenuItemCommand({
- itemKey: key as TNonColorEditorCommands,
- });
- }
+ executeCommand={(item) => {
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
+ editorRef?.executeMenuItemCommand({
+ itemKey: item.itemKey,
+ ...item.extraProps,
+ });
}}
handleAccessChange={handleAccessChange}
handleSubmit={(e) => rest.onEnterKeyPress?.(e)}
isCommentEmpty={isEmpty}
isSubmitting={isSubmitting}
showAccessSpecifier={showAccessSpecifier}
- editorRef={isMutableRefObject(ref) ? ref : null}
+ editorRef={editorRef}
showSubmitButton={showSubmitButton}
/>
diff --git a/web/core/components/editor/lite-text-editor/toolbar.tsx b/web/core/components/editor/lite-text-editor/toolbar.tsx
index ecf8c3283c..1951a3170e 100644
--- a/web/core/components/editor/lite-text-editor/toolbar.tsx
+++ b/web/core/components/editor/lite-text-editor/toolbar.tsx
@@ -3,25 +3,25 @@
import React, { useEffect, useState, useCallback } from "react";
import { Globe2, Lock, LucideIcon } from "lucide-react";
// editor
-import { EditorRefApi, TEditorCommands, TNonColorEditorCommands } from "@plane/editor";
+import { EditorRefApi } from "@plane/editor";
// ui
import { Button, Tooltip } from "@plane/ui";
// constants
-import { TOOLBAR_ITEMS } from "@/constants/editor";
+import { TOOLBAR_ITEMS, ToolbarMenuItem } from "@/constants/editor";
import { EIssueCommentAccessSpecifier } from "@/constants/issue";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
accessSpecifier?: EIssueCommentAccessSpecifier;
- executeCommand: (commandKey: TEditorCommands) => void;
+ executeCommand: (item: ToolbarMenuItem) => void;
handleAccessChange?: (accessKey: EIssueCommentAccessSpecifier) => void;
handleSubmit: (event: React.MouseEvent) => void;
isCommentEmpty: boolean;
isSubmitting: boolean;
showAccessSpecifier: boolean;
showSubmitButton: boolean;
- editorRef: React.MutableRefObject | null;
+ editorRef: EditorRefApi | null;
};
type TCommentAccessType = {
@@ -63,24 +63,25 @@ export const IssueCommentToolbar: React.FC = (props) => {
// Function to update active states
const updateActiveStates = useCallback(() => {
- if (editorRef?.current) {
- const newActiveStates: Record = {};
- Object.values(toolbarItems)
- .flat()
- .forEach((item) => {
- // Assert that editorRef.current is not null
- newActiveStates[item.key] = (editorRef.current as EditorRefApi).isMenuItemActive({
- itemKey: item.key as TNonColorEditorCommands,
- });
+ if (!editorRef) return;
+ const newActiveStates: Record = {};
+ Object.values(toolbarItems)
+ .flat()
+ .forEach((item) => {
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
+ newActiveStates[item.renderKey] = editorRef.isMenuItemActive({
+ itemKey: item.itemKey,
+ ...item.extraProps,
});
- setActiveStates(newActiveStates);
- }
+ });
+ setActiveStates(newActiveStates);
}, [editorRef]);
// useEffect to call updateActiveStates when isActive prop changes
useEffect(() => {
- if (!editorRef?.current) return;
- const unsubscribe = editorRef.current.onStateChange(updateActiveStates);
+ if (!editorRef) return;
+ const unsubscribe = editorRef.onStateChange(updateActiveStates);
updateActiveStates();
return () => unsubscribe();
}, [editorRef, updateActiveStates]);
@@ -122,35 +123,39 @@ export const IssueCommentToolbar: React.FC = (props) => {
"pl-0": index === 0,
})}
>
- {toolbarItems[key].map((item) => (
-
- {item.name}
- {item.shortcut && {item.shortcut.join(" + ")}}
-
- }
- >
- executeCommand(item.key)}
- className={cn(
- "grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-400 hover:bg-custom-background-80",
- {
- "bg-custom-background-80 text-custom-text-100": activeStates[item.key],
- }
- )}
+ {toolbarItems[key].map((item) => {
+ const isItemActive = activeStates[item.renderKey];
+
+ return (
+
+ {item.name}
+ {item.shortcut && {item.shortcut.join(" + ")}}
+
+ }
>
-
-
-
- ))}
+ executeCommand(item)}
+ className={cn(
+ "grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-400 hover:bg-custom-background-80",
+ {
+ "bg-custom-background-80 text-custom-text-100": isItemActive,
+ }
+ )}
+ >
+
+
+
+ );
+ })}
))}
diff --git a/web/core/components/pages/editor/header/color-dropdown.tsx b/web/core/components/pages/editor/header/color-dropdown.tsx
index 68de2dc36b..2809336c17 100644
--- a/web/core/components/pages/editor/header/color-dropdown.tsx
+++ b/web/core/components/pages/editor/header/color-dropdown.tsx
@@ -4,13 +4,19 @@ import { memo } from "react";
import { ALargeSmall, Ban } from "lucide-react";
import { Popover } from "@headlessui/react";
// plane editor
-import { COLORS_LIST, TColorEditorCommands } from "@plane/editor";
+import { COLORS_LIST, TEditorCommands } from "@plane/editor";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
- handleColorSelect: (key: TColorEditorCommands, color: string | undefined) => void;
- isColorActive: (key: TColorEditorCommands, color: string | undefined) => boolean;
+ handleColorSelect: (
+ key: Extract,
+ color: string | undefined
+ ) => void;
+ isColorActive: (
+ key: Extract,
+ color: string | undefined
+ ) => boolean;
};
export const ColorDropdown: React.FC = memo((props) => {
diff --git a/web/core/components/pages/editor/header/toolbar.tsx b/web/core/components/pages/editor/header/toolbar.tsx
index 447616b532..6e4ffdd5f8 100644
--- a/web/core/components/pages/editor/header/toolbar.tsx
+++ b/web/core/components/pages/editor/header/toolbar.tsx
@@ -3,7 +3,7 @@
import React, { useEffect, useState, useCallback } from "react";
import { Check, ChevronDown } from "lucide-react";
// editor
-import { EditorRefApi, TNonColorEditorCommands } from "@plane/editor";
+import { EditorRefApi } from "@plane/editor";
// ui
import { CustomMenu, Tooltip } from "@plane/ui";
// components
@@ -36,11 +36,13 @@ const ToolbarButton: React.FC = React.memo((props) => {
}
>
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
executeCommand({
- itemKey: item.key as TNonColorEditorCommands,
+ itemKey: item.itemKey,
+ ...item.extraProps,
})
}
className={cn("grid size-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", {
@@ -66,15 +68,20 @@ export const PageToolbar: React.FC = ({ editorRef }) => {
const [activeStates, setActiveStates] = useState>({});
const updateActiveStates = useCallback(() => {
+ // console.log("Updating status");
const newActiveStates: Record = {};
Object.values(toolbarItems)
.flat()
.forEach((item) => {
- newActiveStates[item.key] = editorRef.isMenuItemActive({
- itemKey: item.key as TNonColorEditorCommands,
+ // TODO: update this while toolbar homogenization
+ // @ts-expect-error type mismatch here
+ newActiveStates[item.renderKey] = editorRef.isMenuItemActive({
+ itemKey: item.itemKey,
+ ...item.extraProps,
});
});
setActiveStates(newActiveStates);
+ // console.log("newActiveStates", newActiveStates);
}, [editorRef]);
useEffect(() => {
@@ -85,7 +92,8 @@ export const PageToolbar: React.FC = ({ editorRef }) => {
const activeTypography = TYPOGRAPHY_ITEMS.find((item) =>
editorRef.isMenuItemActive({
- itemKey: item.key as TNonColorEditorCommands,
+ itemKey: item.itemKey,
+ ...item.extraProps,
})
);
@@ -105,11 +113,12 @@ export const PageToolbar: React.FC = ({ editorRef }) => {
>
{TYPOGRAPHY_ITEMS.map((item) => (
editorRef.executeMenuItemCommand({
- itemKey: item.key as TNonColorEditorCommands,
+ itemKey: item.itemKey,
+ ...item.extraProps,
})
}
>
@@ -117,7 +126,9 @@ export const PageToolbar: React.FC = ({ editorRef }) => {
{item.name}
- {activeTypography?.key === item.key && }
+ {activeTypography?.itemKey === item.itemKey && (
+
+ )}
))}
@@ -139,9 +150,9 @@ export const PageToolbar: React.FC = ({ editorRef }) => {
{toolbarItems[key].map((item) => (
))}
diff --git a/web/core/constants/editor.ts b/web/core/constants/editor.ts
index 8524a870e2..a1b389fd01 100644
--- a/web/core/constants/editor.ts
+++ b/web/core/constants/editor.ts
@@ -1,5 +1,8 @@
import { Styles, StyleSheet } from "@react-pdf/renderer";
import {
+ AlignCenter,
+ AlignLeft,
+ AlignRight,
Bold,
CaseSensitive,
Code2,
@@ -21,7 +24,7 @@ import {
Underline,
} from "lucide-react";
// editor
-import { TEditorCommands, TEditorFontStyle } from "@plane/editor";
+import { TCommandExtraProps, TEditorCommands, TEditorFontStyle } from "@plane/editor";
// ui
import { MonospaceIcon, SansSerifIcon, SerifIcon } from "@plane/ui";
// helpers
@@ -29,30 +32,95 @@ import { convertRemToPixel } from "@/helpers/common.helper";
type TEditorTypes = "lite" | "document";
-export type ToolbarMenuItem = {
- key: TEditorCommands;
+// Utility type to enforce the necessary extra props or make extraProps optional
+type ExtraPropsForCommand = T extends keyof TCommandExtraProps
+ ? TCommandExtraProps[T]
+ : object; // Default to empty object for commands without extra props
+
+export type ToolbarMenuItem = {
+ itemKey: T;
+ renderKey: string;
name: string;
icon: LucideIcon;
shortcut?: string[];
editors: TEditorTypes[];
+ extraProps?: ExtraPropsForCommand;
};
-export const TYPOGRAPHY_ITEMS: ToolbarMenuItem[] = [
- { key: "text", name: "Text", icon: CaseSensitive, editors: ["document"] },
- { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
- { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
- { key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
- { key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
- { key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
- { key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
+export const TYPOGRAPHY_ITEMS: ToolbarMenuItem<"text" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6">[] = [
+ { itemKey: "text", renderKey: "text", name: "Text", icon: CaseSensitive, editors: ["document"] },
+ { itemKey: "h1", renderKey: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] },
+ { itemKey: "h2", renderKey: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] },
+ { itemKey: "h3", renderKey: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] },
+ { itemKey: "h4", renderKey: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] },
+ { itemKey: "h5", renderKey: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] },
+ { itemKey: "h6", renderKey: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] },
];
-const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
- { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] },
- { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] },
- { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] },
+export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [
{
- key: "strikethrough",
+ itemKey: "text-align",
+ renderKey: "text-align-left",
+ name: "Left align",
+ icon: AlignLeft,
+ shortcut: ["Cmd", "Shift", "L"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "left",
+ },
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-center",
+ name: "Center align",
+ icon: AlignCenter,
+ shortcut: ["Cmd", "Shift", "E"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "center",
+ },
+ },
+ {
+ itemKey: "text-align",
+ renderKey: "text-align-right",
+ name: "Right align",
+ icon: AlignRight,
+ shortcut: ["Cmd", "Shift", "R"],
+ editors: ["lite", "document"],
+ extraProps: {
+ alignment: "right",
+ },
+ },
+];
+
+const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [
+ {
+ itemKey: "bold",
+ renderKey: "bold",
+ name: "Bold",
+ icon: Bold,
+ shortcut: ["Cmd", "B"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "italic",
+ renderKey: "italic",
+ name: "Italic",
+ icon: Italic,
+ shortcut: ["Cmd", "I"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "underline",
+ renderKey: "underline",
+ name: "Underline",
+ icon: Underline,
+ shortcut: ["Cmd", "U"],
+ editors: ["lite", "document"],
+ },
+ {
+ itemKey: "strikethrough",
+ renderKey: "strikethrough",
name: "Strikethrough",
icon: Strikethrough,
shortcut: ["Cmd", "Shift", "S"],
@@ -60,23 +128,26 @@ const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [
},
];
-const LIST_ITEMS: ToolbarMenuItem[] = [
+const LIST_ITEMS: ToolbarMenuItem<"bulleted-list" | "numbered-list" | "to-do-list">[] = [
{
- key: "bulleted-list",
+ itemKey: "bulleted-list",
+ renderKey: "bulleted-list",
name: "Bulleted list",
icon: List,
shortcut: ["Cmd", "Shift", "7"],
editors: ["lite", "document"],
},
{
- key: "numbered-list",
+ itemKey: "numbered-list",
+ renderKey: "numbered-list",
name: "Numbered list",
icon: ListOrdered,
shortcut: ["Cmd", "Shift", "8"],
editors: ["lite", "document"],
},
{
- key: "to-do-list",
+ itemKey: "to-do-list",
+ renderKey: "to-do-list",
name: "To-do list",
icon: ListTodo,
shortcut: ["Cmd", "Shift", "9"],
@@ -84,29 +155,31 @@ const LIST_ITEMS: ToolbarMenuItem[] = [
},
];
-const USER_ACTION_ITEMS: ToolbarMenuItem[] = [
- { key: "quote", name: "Quote", icon: TextQuote, editors: ["lite", "document"] },
- { key: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
+const USER_ACTION_ITEMS: ToolbarMenuItem<"quote" | "code">[] = [
+ { itemKey: "quote", renderKey: "quote", name: "Quote", icon: TextQuote, editors: ["lite", "document"] },
+ { itemKey: "code", renderKey: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
];
-const COMPLEX_ITEMS: ToolbarMenuItem[] = [
- { key: "table", name: "Table", icon: Table, editors: ["document"] },
- { key: "image", name: "Image", icon: Image, editors: ["lite", "document"] },
+const COMPLEX_ITEMS: ToolbarMenuItem<"table" | "image">[] = [
+ { itemKey: "table", renderKey: "table", name: "Table", icon: Table, editors: ["document"] },
+ { itemKey: "image", renderKey: "image", name: "Image", icon: Image, editors: ["lite", "document"], extraProps: {} },
];
export const TOOLBAR_ITEMS: {
[editorType in TEditorTypes]: {
- [key: string]: ToolbarMenuItem[];
+ [key: string]: ToolbarMenuItem[];
};
} = {
lite: {
basic: BASIC_MARK_ITEMS.filter((item) => item.editors.includes("lite")),
+ alignment: TEXT_ALIGNMENT_ITEMS.filter((item) => item.editors.includes("lite")),
list: LIST_ITEMS.filter((item) => item.editors.includes("lite")),
userAction: USER_ACTION_ITEMS.filter((item) => item.editors.includes("lite")),
complex: COMPLEX_ITEMS.filter((item) => item.editors.includes("lite")),
},
document: {
basic: BASIC_MARK_ITEMS.filter((item) => item.editors.includes("document")),
+ alignment: TEXT_ALIGNMENT_ITEMS.filter((item) => item.editors.includes("document")),
list: LIST_ITEMS.filter((item) => item.editors.includes("document")),
userAction: USER_ACTION_ITEMS.filter((item) => item.editors.includes("document")),
complex: COMPLEX_ITEMS.filter((item) => item.editors.includes("document")),
diff --git a/yarn.lock b/yarn.lock
index e0e8dbaa98..cb6f87170e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3597,6 +3597,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.9.1.tgz#e0ca3ec1379dbc39a98070c650d3759df85db794"
integrity sha512-vmUkclPi02iVf+uu74iyUp5xGNib0Gxs73DJ1z+a7CzjuLRqqCa/KEde95CR0Y//DaK/Csz4DOSUyTfLCMvpWg==
+"@tiptap/extension-text-align@^2.8.0":
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.9.1.tgz#5f7920a16c95b283c961cf1e22357bdc355c1626"
+ integrity sha512-oUp0XnwJpAImcOVV68vsY2CpkHpRZ3gzWfIRTuy+aYitQim3xDKis/qfWQUWZsANp9/TZ0VyjtkZxNMwOfcu1g==
+
"@tiptap/extension-text-style@^2.7.1", "@tiptap/extension-text-style@^2.9.1":
version "2.9.1"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz#b9fc9cd8e90747357fbd4cac541a33aaa8b76875"
@@ -3878,9 +3883,9 @@
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz#91f06cda1049e8f17eeab364798ed79c97488a1c"
- integrity sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz#3c9997ae9d00bc236e45c6374e84f2596458d9db"
+ integrity sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==
dependencies:
"@types/node" "*"
"@types/qs" "*"
@@ -4031,11 +4036,11 @@
"@types/node" "*"
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.5.4":
- version "22.7.9"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.9.tgz#2bf2797b5e84702d8262ea2cf843c3c3c880d0e9"
- integrity sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==
+ version "22.8.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.8.0.tgz#193c6f82f9356ce0e6bba86b59f2ffe06e7e320b"
+ integrity sha512-84rafSBHC/z1i1E3p0cJwKA+CfYDNSXX9WSZBRopjIzLET8oNt6ht2tei4C7izwDeEiLLfdeSVBv1egOH916hg==
dependencies:
- undici-types "~6.19.2"
+ undici-types "~6.19.8"
"@types/node@18.14.1":
version "18.14.1"
@@ -4053,9 +4058,9 @@
integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==
"@types/node@^20.14.9", "@types/node@^20.5.2":
- version "20.17.0"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.0.tgz#d0620ba0fe4cf2a0f12351c7bdd805fc4e1f036b"
- integrity sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==
+ version "20.17.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.1.tgz#2b968e060dfb04b7f9550fe3db5f552721c14566"
+ integrity sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==
dependencies:
undici-types "~6.19.2"
@@ -9876,9 +9881,9 @@ postgres-range@^1.1.1:
integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==
posthog-js@^1.131.3:
- version "1.174.4"
- resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.174.4.tgz#67abe7ba9c3b99db8fb472be0017b7d45217184f"
- integrity sha512-wfnSp1nDYHvV4+qy+UnDTED3afe8tVOiLa4Y83RLI2HZdMKovnLq11GJX6cYJ99+hs88HyGD1XmNTxShIQoOhQ==
+ version "1.176.0"
+ resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.176.0.tgz#39841ab213aa9c5500982659dc6d537a407f7205"
+ integrity sha512-T5XKNtRzp7q6CGb7Vc7wAI76rWap9fiuDUPxPsyPBPDkreKya91x9RIsSapAVFafwD1AEin1QMczCmt9Le9BWw==
dependencies:
core-js "^3.38.1"
fflate "^0.4.8"
@@ -10109,9 +10114,9 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, pr
prosemirror-view "^1.27.0"
prosemirror-tables@^1.4.0:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.5.1.tgz#75e6ace7427834f2150f9f08bf8fa400429f5238"
- integrity sha512-zL0vI0rGdhLLKXaZU1Jw1I8RuXwa5bv4aEY6G9TdynNRIU2FodtfI/YdhqVlimilhOIBGMbhvTvnQy5fvbHt2A==
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.0.tgz#b05fbb1172d55dd22ad2662af8e243c969bbbfdd"
+ integrity sha512-eirSS2fwVYzKhvM2qeXSn9ix/SBn7QOLDftPQ4ImEQIevFDiSKAB6Lbrmm/WEgrbTDbCm+xhSq4gOD9w7wT59Q==
dependencies:
prosemirror-keymap "^1.1.2"
prosemirror-model "^1.8.1"
@@ -11735,17 +11740,17 @@ tiptap-markdown@^0.8.9:
markdown-it-task-lists "^2.1.1"
prosemirror-markdown "^1.11.1"
-tldts-core@^6.1.54:
- version "6.1.54"
- resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.54.tgz#a3c3b5f45a64a1f9ea4bb32a94642218c7b7baa5"
- integrity sha512-5cc42+0G0EjYRDfIJHKraaT3I5kPm7j6or3Zh1T9sF+Ftj1T+isT4thicUyQQ1bwN7/xjHQIuY2fXCoXP8Haqg==
+tldts-core@^6.1.55:
+ version "6.1.55"
+ resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.55.tgz#cab0d412672fca9c77d3c51312c69bb5b5ee95c2"
+ integrity sha512-BL+BuKHHaOpntE5BGI6naXjULU6aRlgaYdfDHR3T/hdbNTWkWUZ9yuc11wGnwgpvRwlyUiIK+QohYK3olaVU6Q==
tldts@^6.1.32:
- version "6.1.54"
- resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.54.tgz#782594001a7b95e577b4cc391c0f0ed7c8307d37"
- integrity sha512-rDaL1t59gb/Lg0HPMUGdV1vAKLQcXwU74D26aMaYV4QW7mnMvShd1Vmkg3HYAPWx2JCTUmsrXt/Yl9eJ5UFBQw==
+ version "6.1.55"
+ resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.55.tgz#9a27d1708652bbae93d4b842dc2f8554fdabffc6"
+ integrity sha512-HxQR/9roQ07Pwc8RyyrJMAxRz5/ssoF3qIPPUiIo3zUt6yMdmYZjM2OZIFMiZ3jHyz9jrGHEHuQZrUhoc1LkDw==
dependencies:
- tldts-core "^6.1.54"
+ tldts-core "^6.1.55"
to-regex-range@^5.0.1:
version "5.0.1"
@@ -12063,7 +12068,7 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
-undici-types@~6.19.2:
+undici-types@~6.19.2, undici-types@~6.19.8:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
From 45a5cf5119124c4dfe9dd942dbf508caccdfe420 Mon Sep 17 00:00:00 2001
From: Akshita Goyal <36129505+gakshita@users.noreply.github.com>
Date: Tue, 5 Nov 2024 17:47:39 +0530
Subject: [PATCH 5/7] fix: editor height (#5953)
* fix: editor height
* fix: removed unwanted class
* fix: editor height
---
space/core/components/editor/rich-text-editor.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/space/core/components/editor/rich-text-editor.tsx b/space/core/components/editor/rich-text-editor.tsx
index af4cf97136..39a18f4f4c 100644
--- a/space/core/components/editor/rich-text-editor.tsx
+++ b/space/core/components/editor/rich-text-editor.tsx
@@ -33,7 +33,8 @@ export const RichTextEditor = forwardRef
);
});
From 438d1bcfbd03cae25b2406a5e1ca67b4eb6497ab Mon Sep 17 00:00:00 2001
From: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Date: Tue, 5 Nov 2024 17:50:23 +0530
Subject: [PATCH 6/7] add missing config to get issues api call (#5955)
---
web/core/local-db/storage.sqlite.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts
index 761476bb49..55f3e54ce0 100644
--- a/web/core/local-db/storage.sqlite.ts
+++ b/web/core/local-db/storage.sqlite.ts
@@ -310,7 +310,7 @@ export class Storage {
} catch (e) {
logError(e);
const issueService = new IssueService();
- return await issueService.getIssuesFromServer(workspaceSlug, projectId, queries);
+ return await issueService.getIssuesFromServer(workspaceSlug, projectId, queries, config);
}
const end = performance.now();
From 56755b0e9c684313c978776fc7343905675e1d78 Mon Sep 17 00:00:00 2001
From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Date: Tue, 5 Nov 2024 19:21:20 +0530
Subject: [PATCH 7/7] chore: intake migration (#5950)
* chore: intake migration
* chore: removed the enum
* chore: removed the source type enum
* chore: changed the migration file
---
...e_project_when_deleted_at_null_and_more.py | 75 +++++++++++++++++++
apiserver/plane/db/models/deploy_board.py | 5 +-
apiserver/plane/db/models/inbox.py | 9 ++-
apiserver/plane/db/models/label.py | 16 ++--
apiserver/plane/db/models/user.py | 6 ++
5 files changed, 104 insertions(+), 7 deletions(-)
create mode 100644 apiserver/plane/db/migrations/0084_remove_label_label_unique_name_project_when_deleted_at_null_and_more.py
diff --git a/apiserver/plane/db/migrations/0084_remove_label_label_unique_name_project_when_deleted_at_null_and_more.py b/apiserver/plane/db/migrations/0084_remove_label_label_unique_name_project_when_deleted_at_null_and_more.py
new file mode 100644
index 0000000000..25bfcb8fb3
--- /dev/null
+++ b/apiserver/plane/db/migrations/0084_remove_label_label_unique_name_project_when_deleted_at_null_and_more.py
@@ -0,0 +1,75 @@
+# Generated by Django 4.2.15 on 2024-11-05 07:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("db", "0083_device_workspace_timezone_and_more"),
+ ]
+
+ operations = [
+ migrations.RemoveConstraint(
+ model_name="label",
+ name="label_unique_name_project_when_deleted_at_null",
+ ),
+ migrations.AlterUniqueTogether(
+ name="label",
+ unique_together=set(),
+ ),
+ migrations.AddField(
+ model_name="deployboard",
+ name="is_disabled",
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name="inboxissue",
+ name="extra",
+ field=models.JSONField(default=dict),
+ ),
+ migrations.AddField(
+ model_name="inboxissue",
+ name="source_email",
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="user",
+ name="bot_type",
+ field=models.CharField(
+ blank=True, max_length=30, null=True, verbose_name="Bot Type"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="deployboard",
+ name="entity_name",
+ field=models.CharField(blank=True, max_length=30, null=True),
+ ),
+ migrations.AlterField(
+ model_name="inboxissue",
+ name="source",
+ field=models.CharField(
+ blank=True, default="IN_APP", max_length=255, null=True
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name="label",
+ constraint=models.UniqueConstraint(
+ condition=models.Q(
+ ("deleted_at__isnull", True), ("project__isnull", True)
+ ),
+ fields=("name",),
+ name="unique_name_when_project_null_and_not_deleted",
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name="label",
+ constraint=models.UniqueConstraint(
+ condition=models.Q(
+ ("deleted_at__isnull", True), ("project__isnull", False)
+ ),
+ fields=("project", "name"),
+ name="unique_project_name_when_not_deleted",
+ ),
+ ),
+ ]
diff --git a/apiserver/plane/db/models/deploy_board.py b/apiserver/plane/db/models/deploy_board.py
index da9476f16c..2bffe57962 100644
--- a/apiserver/plane/db/models/deploy_board.py
+++ b/apiserver/plane/db/models/deploy_board.py
@@ -20,12 +20,14 @@ class DeployBoard(WorkspaceBaseModel):
("cycle", "Task"),
("page", "Page"),
("view", "View"),
+ ("intake", "Intake"),
)
entity_identifier = models.UUIDField(null=True)
entity_name = models.CharField(
max_length=30,
- choices=TYPE_CHOICES,
+ null=True,
+ blank=True,
)
anchor = models.CharField(
max_length=255, default=get_anchor, unique=True, db_index=True
@@ -41,6 +43,7 @@ class DeployBoard(WorkspaceBaseModel):
is_votes_enabled = models.BooleanField(default=False)
view_props = models.JSONField(default=dict)
is_activity_enabled = models.BooleanField(default=True)
+ is_disabled = models.BooleanField(default=False)
def __str__(self):
"""Return name of the deploy board"""
diff --git a/apiserver/plane/db/models/inbox.py b/apiserver/plane/db/models/inbox.py
index be2b1f3dd2..95c84be46f 100644
--- a/apiserver/plane/db/models/inbox.py
+++ b/apiserver/plane/db/models/inbox.py
@@ -57,9 +57,16 @@ class InboxIssue(ProjectBaseModel):
on_delete=models.SET_NULL,
null=True,
)
- source = models.TextField(blank=True, null=True)
+ source = models.CharField(
+ max_length=255,
+ default="IN_APP",
+ null=True,
+ blank=True,
+ )
+ source_email = models.TextField(blank=True, null=True)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
+ extra = models.JSONField(default=dict)
class Meta:
verbose_name = "InboxIssue"
diff --git a/apiserver/plane/db/models/label.py b/apiserver/plane/db/models/label.py
index 7070ef9e03..11e2da8c32 100644
--- a/apiserver/plane/db/models/label.py
+++ b/apiserver/plane/db/models/label.py
@@ -20,13 +20,19 @@ class Label(WorkspaceBaseModel):
external_id = models.CharField(max_length=255, blank=True, null=True)
class Meta:
- unique_together = ["name", "project", "deleted_at"]
constraints = [
+ # Enforce uniqueness of name when project is NULL and deleted_at is NULL
models.UniqueConstraint(
- fields=["name", "project"],
- condition=Q(deleted_at__isnull=True),
- name="label_unique_name_project_when_deleted_at_null",
- )
+ fields=["name"],
+ condition=Q(project__isnull=True, deleted_at__isnull=True),
+ name="unique_name_when_project_null_and_not_deleted",
+ ),
+ # Enforce uniqueness of project and name when project is not NULL and deleted_at is NULL
+ models.UniqueConstraint(
+ fields=["project", "name"],
+ condition=Q(project__isnull=False, deleted_at__isnull=True),
+ name="unique_project_name_when_not_deleted",
+ ),
]
verbose_name = "Label"
verbose_name_plural = "Labels"
diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py
index 3b84ec60e2..e97d04e7a4 100644
--- a/apiserver/plane/db/models/user.py
+++ b/apiserver/plane/db/models/user.py
@@ -107,6 +107,12 @@ class User(AbstractBaseUser, PermissionsMixin):
# my_issues_prop = models.JSONField(null=True)
is_bot = models.BooleanField(default=False)
+ bot_type = models.CharField(
+ max_length=30,
+ verbose_name="Bot Type",
+ blank=True,
+ null=True,
+ )
# timezone
USER_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))