From d0f9a4d2457e0b2488054dc88e611d59fb23114e Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 29 Nov 2024 20:20:49 +0530 Subject: [PATCH 01/52] chore: add redirection to plane logo in invitations page (#6125) --- web/app/invitations/page.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/app/invitations/page.tsx b/web/app/invitations/page.tsx index 580896bb5..73ef15b63 100644 --- a/web/app/invitations/page.tsx +++ b/web/app/invitations/page.tsx @@ -136,11 +136,14 @@ const UserInvitationsPage = observer(() => {
-
+
Plane logo
-
+
{currentUser?.email}
From 75ada1bfacac0dcfda6a7c9f00d1802487616420 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Sun, 1 Dec 2024 21:26:35 +0530 Subject: [PATCH 02/52] fix: constants package updates --- packages/constants/package.json | 2 +- packages/constants/{ => src}/auth.ts | 0 packages/constants/src/endpoints.ts | 15 ++++++ packages/constants/{ => src}/index.ts | 1 + packages/constants/{ => src}/issue.ts | 0 packages/constants/src/workspace.ts | 76 +++++++++++++++++++++++++++ packages/constants/workspace.ts | 23 -------- 7 files changed, 93 insertions(+), 24 deletions(-) rename packages/constants/{ => src}/auth.ts (100%) create mode 100644 packages/constants/src/endpoints.ts rename packages/constants/{ => src}/index.ts (72%) rename packages/constants/{ => src}/issue.ts (100%) create mode 100644 packages/constants/src/workspace.ts delete mode 100644 packages/constants/workspace.ts diff --git a/packages/constants/package.json b/packages/constants/package.json index 5271ee3b4..ce02a4946 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -2,5 +2,5 @@ "name": "@plane/constants", "version": "0.24.0", "private": true, - "main": "./index.ts" + "main": "./src/index.ts" } diff --git a/packages/constants/auth.ts b/packages/constants/src/auth.ts similarity index 100% rename from packages/constants/auth.ts rename to packages/constants/src/auth.ts diff --git a/packages/constants/src/endpoints.ts b/packages/constants/src/endpoints.ts new file mode 100644 index 000000000..751ee20dd --- /dev/null +++ b/packages/constants/src/endpoints.ts @@ -0,0 +1,15 @@ +export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || ""; +// PI Base Url +export const PI_BASE_URL = process.env.NEXT_PUBLIC_PI_BASE_URL || ""; +// God Mode Admin App Base Url +export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || ""; +export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || ""; +export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}/`); +// Publish App Base Url +export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || ""; +export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || ""; +export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}/`); +// Live App Base Url +export const LIVE_BASE_URL = process.env.NEXT_PUBLIC_LIVE_BASE_URL || ""; +export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || ""; +export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}/`); diff --git a/packages/constants/index.ts b/packages/constants/src/index.ts similarity index 72% rename from packages/constants/index.ts rename to packages/constants/src/index.ts index 85e95bf4e..418908622 100644 --- a/packages/constants/index.ts +++ b/packages/constants/src/index.ts @@ -1,3 +1,4 @@ export * from "./auth"; +export * from "./endpoints"; export * from "./issue"; export * from "./workspace"; diff --git a/packages/constants/issue.ts b/packages/constants/src/issue.ts similarity index 100% rename from packages/constants/issue.ts rename to packages/constants/src/issue.ts diff --git a/packages/constants/src/workspace.ts b/packages/constants/src/workspace.ts new file mode 100644 index 000000000..c17b5432e --- /dev/null +++ b/packages/constants/src/workspace.ts @@ -0,0 +1,76 @@ +export const ORGANIZATION_SIZE = [ + "Just myself", + "2-10", + "11-50", + "51-200", + "201-500", + "500+", +]; + +export const RESTRICTED_URLS = [ + "404", + "accounts", + "api", + "create-workspace", + "god-mode", + "installations", + "invitations", + "onboarding", + "profile", + "spaces", + "workspace-invitations", + "password", + "flags", + "monitor", + "monitoring", + "ingest", + "plane-pro", + "plane-ultimate", + "enterprise", + "plane-enterprise", + "disco", + "silo", + "chat", + "calendar", + "drive", + "channels", + "upgrade", + "billing", + "sign-in", + "sign-up", + "signin", + "signup", + "config", + "live", + "admin", + "m", + "import", + "importers", + "integrations", + "integration", + "configuration", + "initiatives", + "initiative", + "config", + "workflow", + "workflows", + "epics", + "epic", + "story", + "mobile", + "dashboard", + "desktop", + "onload", + "real-time", + "one", + "pages", + "mobile", + "business", + "pro", + "settings", + "monitor", + "license", + "licenses", + "instances", + "instance", +]; diff --git a/packages/constants/workspace.ts b/packages/constants/workspace.ts deleted file mode 100644 index 32f36de1b..000000000 --- a/packages/constants/workspace.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const ORGANIZATION_SIZE = [ - "Just myself", - "2-10", - "11-50", - "51-200", - "201-500", - "500+", -]; - -export const RESTRICTED_URLS = [ - "404", - "accounts", - "api", - "create-workspace", - "error", - "god-mode", - "installations", - "invitations", - "onboarding", - "profile", - "spaces", - "workspace-invitations", -]; From 1b9033993dd4a1dedbc8f34c5c137d6092aa64f2 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:22:08 +0530 Subject: [PATCH 03/52] [WEB-2799] chore: global component and code refactor (#6131) * chore: local storage helper hook added to package * chore: tabs global component added * chore: collapsible button improvement * chore: linear progress indicator improvement * chore: fill icon set added to package --- packages/helpers/hooks/index.ts | 1 + packages/helpers/hooks/use-local-storage.tsx | 59 ++++++++++++ .../ui/src/collapsible/collapsible-button.tsx | 21 ++++- packages/ui/src/icons/comment-fill-icon.tsx | 14 +++ packages/ui/src/icons/epic-icon.tsx | 28 ++++++ packages/ui/src/icons/index.ts | 3 + packages/ui/src/icons/info-fill-icon.tsx | 14 +++ packages/ui/src/index.ts | 1 + .../progress/linear-progress-indicator.tsx | 17 ++-- packages/ui/src/tabs/index.ts | 1 + packages/ui/src/tabs/tabs.tsx | 94 +++++++++++++++++++ 11 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 packages/helpers/hooks/use-local-storage.tsx create mode 100644 packages/ui/src/icons/comment-fill-icon.tsx create mode 100644 packages/ui/src/icons/epic-icon.tsx create mode 100644 packages/ui/src/icons/info-fill-icon.tsx create mode 100644 packages/ui/src/tabs/index.ts create mode 100644 packages/ui/src/tabs/tabs.tsx diff --git a/packages/helpers/hooks/index.ts b/packages/helpers/hooks/index.ts index c7a8f4c06..c07642907 100644 --- a/packages/helpers/hooks/index.ts +++ b/packages/helpers/hooks/index.ts @@ -1 +1,2 @@ +export * from "./use-local-storage"; export * from "./use-outside-click-detector"; diff --git a/packages/helpers/hooks/use-local-storage.tsx b/packages/helpers/hooks/use-local-storage.tsx new file mode 100644 index 000000000..f04e0e71b --- /dev/null +++ b/packages/helpers/hooks/use-local-storage.tsx @@ -0,0 +1,59 @@ +import { useState, useEffect, useCallback } from "react"; + +export const getValueFromLocalStorage = (key: string, defaultValue: any) => { + if (typeof window === undefined || typeof window === "undefined") + return defaultValue; + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : defaultValue; + } catch (error) { + window.localStorage.removeItem(key); + return defaultValue; + } +}; + +export const setValueIntoLocalStorage = (key: string, value: any) => { + if (typeof window === undefined || typeof window === "undefined") + return false; + try { + window.localStorage.setItem(key, JSON.stringify(value)); + return true; + } catch (error) { + return false; + } +}; + +export const useLocalStorage = (key: string, initialValue: T) => { + const [storedValue, setStoredValue] = useState(() => + getValueFromLocalStorage(key, initialValue) + ); + + const setValue = useCallback( + (value: T) => { + window.localStorage.setItem(key, JSON.stringify(value)); + setStoredValue(value); + window.dispatchEvent(new Event(`local-storage:${key}`)); + }, + [key] + ); + + const clearValue = useCallback(() => { + window.localStorage.removeItem(key); + setStoredValue(null); + window.dispatchEvent(new Event(`local-storage:${key}`)); + }, [key]); + + const reHydrate = useCallback(() => { + const data = getValueFromLocalStorage(key, initialValue); + setStoredValue(data); + }, [key, initialValue]); + + useEffect(() => { + window.addEventListener(`local-storage:${key}`, reHydrate); + return () => { + window.removeEventListener(`local-storage:${key}`, reHydrate); + }; + }, [key, reHydrate]); + + return { storedValue, setValue, clearValue } as const; +}; diff --git a/packages/ui/src/collapsible/collapsible-button.tsx b/packages/ui/src/collapsible/collapsible-button.tsx index a56a724b4..2a141aa41 100644 --- a/packages/ui/src/collapsible/collapsible-button.tsx +++ b/packages/ui/src/collapsible/collapsible-button.tsx @@ -8,12 +8,27 @@ type Props = { hideChevron?: boolean; indicatorElement?: React.ReactNode; actionItemElement?: React.ReactNode; + className?: string; + titleClassName?: string; }; export const CollapsibleButton: FC = (props) => { - const { isOpen, title, hideChevron = false, indicatorElement, actionItemElement } = props; + const { + isOpen, + title, + hideChevron = false, + indicatorElement, + actionItemElement, + className = "", + titleClassName = "", + } = props; return ( -
+
{!hideChevron && ( @@ -23,7 +38,7 @@ export const CollapsibleButton: FC = (props) => { })} /> )} - {title} + {title}
{indicatorElement && indicatorElement}
diff --git a/packages/ui/src/icons/comment-fill-icon.tsx b/packages/ui/src/icons/comment-fill-icon.tsx new file mode 100644 index 000000000..8825fbc79 --- /dev/null +++ b/packages/ui/src/icons/comment-fill-icon.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import { ISvgIcons } from "./type"; + +export const CommentFillIcon: React.FC = ({ className = "text-current", ...rest }) => ( + + + +); diff --git a/packages/ui/src/icons/epic-icon.tsx b/packages/ui/src/icons/epic-icon.tsx new file mode 100644 index 000000000..c78a98261 --- /dev/null +++ b/packages/ui/src/icons/epic-icon.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; + +export type Props = { + className?: string; + width?: string | number; + height?: string | number; + color?: string; +}; + +export const EpicIcon: React.FC = ({ width = "16", height = "16", className }) => ( + + + + +); diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 91ae0e2f1..1402dedb0 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -7,12 +7,15 @@ export * from "./blocker-icon"; export * from "./calendar-after-icon"; export * from "./calendar-before-icon"; export * from "./center-panel-icon"; +export * from "./comment-fill-icon"; export * from "./create-icon"; export * from "./dice-icon"; export * from "./discord-icon"; +export * from "./epic-icon"; export * from "./full-screen-panel-icon"; export * from "./github-icon"; export * from "./gitlab-icon"; +export * from "./info-icon"; export * from "./layer-stack"; export * from "./layers-icon"; export * from "./monospace-icon"; diff --git a/packages/ui/src/icons/info-fill-icon.tsx b/packages/ui/src/icons/info-fill-icon.tsx new file mode 100644 index 000000000..c7416e45d --- /dev/null +++ b/packages/ui/src/icons/info-fill-icon.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import { ISvgIcons } from "./type"; + +export const InfoFillIcon: React.FC = ({ className = "text-current", ...rest }) => ( + + + +); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0af3b0469..279e19a3e 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -28,3 +28,4 @@ export * from "./row"; export * from "./content-wrapper"; export * from "./card"; export * from "./tag"; +export * from "./tabs"; diff --git a/packages/ui/src/progress/linear-progress-indicator.tsx b/packages/ui/src/progress/linear-progress-indicator.tsx index de8af4cbc..e32087d49 100644 --- a/packages/ui/src/progress/linear-progress-indicator.tsx +++ b/packages/ui/src/progress/linear-progress-indicator.tsx @@ -6,7 +6,9 @@ type Props = { data: any; noTooltip?: boolean; inPercentage?: boolean; - size?: "sm" | "md" | "lg"; + size?: "sm" | "md" | "lg" | "xl"; + className?: string; + barClassName?: string; }; export const LinearProgressIndicator: React.FC = ({ @@ -14,6 +16,8 @@ export const LinearProgressIndicator: React.FC = ({ noTooltip = false, inPercentage = false, size = "sm", + className = "", + barClassName = "", }) => { const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -31,7 +35,7 @@ export const LinearProgressIndicator: React.FC = ({ else return ( -
+
); }); @@ -42,13 +46,12 @@ export const LinearProgressIndicator: React.FC = ({ "h-2": size === "sm", "h-3": size === "md", "h-3.5": size === "lg", + "h-[14px]": size === "xl", })} > - {total === 0 ? ( -
{bars}
- ) : ( -
{bars}
- )} +
+ {bars} +
); }; diff --git a/packages/ui/src/tabs/index.ts b/packages/ui/src/tabs/index.ts new file mode 100644 index 000000000..811d3d4a7 --- /dev/null +++ b/packages/ui/src/tabs/index.ts @@ -0,0 +1 @@ +export * from "./tabs"; diff --git a/packages/ui/src/tabs/tabs.tsx b/packages/ui/src/tabs/tabs.tsx new file mode 100644 index 000000000..e2fe8602c --- /dev/null +++ b/packages/ui/src/tabs/tabs.tsx @@ -0,0 +1,94 @@ +import React, { FC, Fragment } from "react"; +import { Tab } from "@headlessui/react"; +import { LucideProps } from "lucide-react"; +// helpers +import { useLocalStorage } from "@plane/helpers"; +import { cn } from "../../helpers"; + +type TabItem = { + key: string; + icon?: FC; + label?: React.ReactNode; + content: React.ReactNode; + disabled?: boolean; +}; + +type TTabsProps = { + tabs: TabItem[]; + storageKey: string; + actions?: React.ReactNode; + defaultTab?: string; + containerClassName?: string; + tabListContainerClassName?: string; + tabListClassName?: string; + tabClassName?: string; + tabPanelClassName?: string; +}; + +export const Tabs: FC = (props: TTabsProps) => { + const { + tabs, + storageKey, + actions, + defaultTab = tabs[0]?.key, + containerClassName = "", + tabListContainerClassName = "", + tabListClassName = "", + tabClassName = "", + tabPanelClassName = "", + } = props; + // local storage + const { storedValue, setValue } = useLocalStorage(`tab-${storageKey}`, defaultTab); + + const currentTabIndex = (tabKey: string): number => tabs.findIndex((tab) => tab.key === tabKey); + + return ( +
+ +
+
+ + {tabs.map((tab) => ( + + cn( + `flex items-center justify-center p-1 min-w-fit w-full font-medium text-custom-text-100 outline-none focus:outline-none cursor-pointer transition-all rounded`, + selected + ? "bg-custom-background-100 text-custom-text-100 shadow-sm" + : tab.disabled + ? "text-custom-text-400 cursor-not-allowed" + : "text-custom-text-400 hover:text-custom-text-300 hover:bg-custom-background-80/60", + tabClassName + ) + } + key={tab.key} + onClick={() => { + if (!tab.disabled) setValue(tab.key); + }} + disabled={tab.disabled} + > + {tab.icon && } + {tab.label} + + ))} + + {actions &&
{actions}
} +
+ + {tabs.map((tab) => ( + + {tab.content} + + ))} + +
+
+
+ ); +}; From 1953d6fe3aed5841c00f9a669594c18841223965 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:24:01 +0530 Subject: [PATCH 04/52] [WEB-2762] chore: loader code refactor (#5992) * chore: loader code refactor * chore: code refactor * chore: code refactor * chore: code refactor --- packages/ui/src/dropdown/common/loader.tsx | 5 +++-- .../projects/(detail)/[projectId]/settings/sidebar.tsx | 3 ++- .../dashboard/widgets/loaders/issues-by-state-group.tsx | 3 ++- .../dashboard/widgets/loaders/overview-stats.tsx | 3 ++- .../dashboard/widgets/loaders/recent-activity.tsx | 3 ++- .../dashboard/widgets/loaders/recent-collaborators.tsx | 3 ++- .../dashboard/widgets/loaders/recent-projects.tsx | 3 ++- web/core/components/issues/workspace-draft/loader.tsx | 3 ++- web/core/components/pages/loaders/page-loader.tsx | 3 ++- .../components/ui/loader/cycle-module-board-loader.tsx | 4 +++- .../components/ui/loader/cycle-module-list-loader.tsx | 4 +++- .../ui/loader/layouts/calendar-layout-loader.tsx | 9 +++++---- .../components/ui/loader/layouts/gantt-layout-loader.tsx | 9 +++++---- .../ui/loader/layouts/kanban-layout-loader.tsx | 3 ++- .../components/ui/loader/layouts/list-layout-loader.tsx | 5 +++-- .../ui/loader/layouts/members-layout-loader.tsx | 5 +++-- .../layouts/project-inbox/inbox-sidebar-loader.tsx | 3 ++- .../ui/loader/layouts/spreadsheet-layout-loader.tsx | 7 ++++--- web/core/components/ui/loader/notification-loader.tsx | 4 +++- web/core/components/ui/loader/pages-loader.tsx | 6 ++++-- web/core/components/ui/loader/projects-loader.tsx | 4 +++- web/core/components/ui/loader/settings/activity.tsx | 3 ++- web/core/components/ui/loader/settings/api-token.tsx | 4 +++- web/core/components/ui/loader/settings/email.tsx | 4 +++- .../components/ui/loader/settings/import-and-export.tsx | 4 +++- web/core/components/ui/loader/settings/integration.tsx | 4 +++- web/core/components/ui/loader/settings/members.tsx | 4 +++- web/core/components/ui/loader/view-list-loader.tsx | 4 +++- web/core/components/web-hooks/form/secret-key.tsx | 3 ++- .../workspace-notifications/sidebar/loader.tsx | 4 +++- 30 files changed, 84 insertions(+), 42 deletions(-) diff --git a/packages/ui/src/dropdown/common/loader.tsx b/packages/ui/src/dropdown/common/loader.tsx index 0ec1f053b..814ed4805 100644 --- a/packages/ui/src/dropdown/common/loader.tsx +++ b/packages/ui/src/dropdown/common/loader.tsx @@ -1,9 +1,10 @@ +import range from "lodash/range"; import React from "react"; export const DropdownOptionsLoader = () => (
- {Array.from({ length: 6 }, (_, i) => ( -
+ {range(6).map((index) => ( +
))}
); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx index c3aa909ad..7a352ce0f 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; +import range from "lodash/range"; import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; @@ -29,7 +30,7 @@ export const ProjectSettingsSidebar = observer(() => {
SETTINGS - {[...Array(8)].map((index) => ( + {range(8).map((index) => ( ))} diff --git a/web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx b/web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx index d44eb1e26..099323cce 100644 --- a/web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx +++ b/web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx @@ -1,5 +1,6 @@ "use client"; +import range from "lodash/range"; // ui import { Loader } from "@plane/ui"; @@ -14,7 +15,7 @@ export const IssuesByStateGroupWidgetLoader = () => (
- {Array.from({ length: 5 }).map((_, index) => ( + {range(5).map((index) => ( ))}
diff --git a/web/core/components/dashboard/widgets/loaders/overview-stats.tsx b/web/core/components/dashboard/widgets/loaders/overview-stats.tsx index 2b634792e..e780bb399 100644 --- a/web/core/components/dashboard/widgets/loaders/overview-stats.tsx +++ b/web/core/components/dashboard/widgets/loaders/overview-stats.tsx @@ -1,11 +1,12 @@ "use client"; +import range from "lodash/range"; // ui import { Loader } from "@plane/ui"; export const OverviewStatsWidgetLoader = () => ( - {Array.from({ length: 4 }).map((_, index) => ( + {range(4).map((index) => (
diff --git a/web/core/components/dashboard/widgets/loaders/recent-activity.tsx b/web/core/components/dashboard/widgets/loaders/recent-activity.tsx index 3a280dad7..2df78a15a 100644 --- a/web/core/components/dashboard/widgets/loaders/recent-activity.tsx +++ b/web/core/components/dashboard/widgets/loaders/recent-activity.tsx @@ -1,12 +1,13 @@ "use client"; +import range from "lodash/range"; // ui import { Loader } from "@plane/ui"; export const RecentActivityWidgetLoader = () => ( - {Array.from({ length: 7 }).map((_, index) => ( + {range(7).map((index) => (
diff --git a/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx b/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx index 92715fb88..2dceaf132 100644 --- a/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx +++ b/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx @@ -1,11 +1,12 @@ "use client"; +import range from "lodash/range"; // ui import { Loader } from "@plane/ui"; export const RecentCollaboratorsWidgetLoader = () => ( <> - {Array.from({ length: 8 }).map((_, index) => ( + {range(8).map((index) => (
diff --git a/web/core/components/dashboard/widgets/loaders/recent-projects.tsx b/web/core/components/dashboard/widgets/loaders/recent-projects.tsx index 8bd652c3b..38bc7e29a 100644 --- a/web/core/components/dashboard/widgets/loaders/recent-projects.tsx +++ b/web/core/components/dashboard/widgets/loaders/recent-projects.tsx @@ -1,12 +1,13 @@ "use client"; +import range from "lodash/range"; // ui import { Loader } from "@plane/ui"; export const RecentProjectsWidgetLoader = () => ( - {Array.from({ length: 5 }).map((_, index) => ( + {range(5).map((index) => (
diff --git a/web/core/components/issues/workspace-draft/loader.tsx b/web/core/components/issues/workspace-draft/loader.tsx index d663a0d03..56efcea98 100644 --- a/web/core/components/issues/workspace-draft/loader.tsx +++ b/web/core/components/issues/workspace-draft/loader.tsx @@ -1,6 +1,7 @@ "use client"; import { FC } from "react"; +import range from "lodash/range"; // components import { ListLoaderItemRow } from "@/components/ui"; @@ -12,7 +13,7 @@ export const WorkspaceDraftIssuesLoader: FC = (prop const { items = 14 } = props; return (
- {[...Array(items)].map((_, index) => ( + {range(items).map((index) => ( ))}
diff --git a/web/core/components/pages/loaders/page-loader.tsx b/web/core/components/pages/loaders/page-loader.tsx index e2e0c09b4..974c28f6b 100644 --- a/web/core/components/pages/loaders/page-loader.tsx +++ b/web/core/components/pages/loaders/page-loader.tsx @@ -1,5 +1,6 @@ "use client"; +import range from "lodash/range"; import { Loader } from "@plane/ui"; export const PageLoader: React.FC = (props) => { @@ -17,7 +18,7 @@ export const PageLoader: React.FC = (props) => {
- {Array.from(Array(10)).map((i) => ( + {range(10).map((i) => (
diff --git a/web/core/components/ui/loader/cycle-module-board-loader.tsx b/web/core/components/ui/loader/cycle-module-board-loader.tsx index f88719c38..62df0e465 100644 --- a/web/core/components/ui/loader/cycle-module-board-loader.tsx +++ b/web/core/components/ui/loader/cycle-module-board-loader.tsx @@ -1,8 +1,10 @@ +import range from "lodash/range"; + export const CycleModuleBoardLayout = () => (
- {[...Array(5)].map((i) => ( + {range(5).map((i) => (
(
- {[...Array(5)].map((i) => ( + {range(5).map((i) => (
{ const dataCount = getRandomInt(0, 1); - const dataBlocks = Array.from({ length: dataCount }, (_, index) => ( + const dataBlocks = range(dataCount).map((index) => ( )); @@ -19,15 +20,15 @@ const CalendarDay = () => { export const CalendarLayoutLoader = () => (
- {[...Array(5)].map((_, index) => ( + {range(5).map((index) => ( ))}
- {[...Array(6)].map((_, index) => ( + {range(6).map((index) => (
- {[...Array(5)].map((_, index) => ( + {range(5).map((index) => ( ))}
diff --git a/web/core/components/ui/loader/layouts/gantt-layout-loader.tsx b/web/core/components/ui/loader/layouts/gantt-layout-loader.tsx index bb4cd47e2..50a5ac56a 100644 --- a/web/core/components/ui/loader/layouts/gantt-layout-loader.tsx +++ b/web/core/components/ui/loader/layouts/gantt-layout-loader.tsx @@ -1,9 +1,10 @@ +import range from "lodash/range"; import { Row } from "@plane/ui"; import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants"; import { getRandomLength } from "../utils"; export const GanttLayoutLIstItem = () => ( -
+
@@ -23,7 +24,7 @@ export const GanttLayoutLoader = () => (
- {[...Array(6)].map((_, index) => ( + {range(6).map((index) => (
@@ -37,13 +38,13 @@ export const GanttLayoutLoader = () => (
- {[...Array(15)].map((_, index) => ( + {range(15).map((index) => ( ))}
- {[...Array(6)].map((_, index) => ( + {range(6).map((index) => (
)} - {Array.from({ length: cardsInColumn }, (_, cardIndex) => ( + {range(cardsInColumn).map((cardIndex) => ( ))}
diff --git a/web/core/components/ui/loader/layouts/list-layout-loader.tsx b/web/core/components/ui/loader/layouts/list-layout-loader.tsx index bcada0370..65d4d1565 100644 --- a/web/core/components/ui/loader/layouts/list-layout-loader.tsx +++ b/web/core/components/ui/loader/layouts/list-layout-loader.tsx @@ -1,4 +1,5 @@ import { Fragment, forwardRef } from "react"; +import range from "lodash/range"; import { cn } from "@plane/editor"; import { Row } from "@plane/ui"; import { getRandomInt, getRandomLength } from "../utils"; @@ -29,7 +30,7 @@ export const ListLoaderItemRow = forwardRef< />
- {[...Array(defaultPropertyCount)].map((_, index) => ( + {range(defaultPropertyCount).map((index) => ( {getRandomInt(1, 2) % 2 === 0 ? ( (
- {[...Array(itemCount)].map((_, index) => ( + {range(itemCount).map((index) => ( ))}
diff --git a/web/core/components/ui/loader/layouts/members-layout-loader.tsx b/web/core/components/ui/loader/layouts/members-layout-loader.tsx index ae8363556..6010d17bf 100644 --- a/web/core/components/ui/loader/layouts/members-layout-loader.tsx +++ b/web/core/components/ui/loader/layouts/members-layout-loader.tsx @@ -1,11 +1,12 @@ +import range from "lodash/range"; export const MembersLayoutLoader = () => (
- {Array.from({ length: 5 }, (_, columnIndex) => ( + {range(5).map((columnIndex) => (
- {Array.from({ length: 2 }, (_, cardIndex) => ( + {range(2).map((cardIndex) => ( ))}
diff --git a/web/core/components/ui/loader/layouts/project-inbox/inbox-sidebar-loader.tsx b/web/core/components/ui/loader/layouts/project-inbox/inbox-sidebar-loader.tsx index 01da38f6a..0ead51f58 100644 --- a/web/core/components/ui/loader/layouts/project-inbox/inbox-sidebar-loader.tsx +++ b/web/core/components/ui/loader/layouts/project-inbox/inbox-sidebar-loader.tsx @@ -1,8 +1,9 @@ import React from "react"; +import range from "lodash/range"; export const InboxSidebarLoader = () => (
- {[...Array(6)].map((i, index) => ( + {range(6).map((index) => (
diff --git a/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx b/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx index 370f731ed..c1b00d0ad 100644 --- a/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx +++ b/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx @@ -1,3 +1,4 @@ +import range from "lodash/range"; import { Row } from "@plane/ui"; import { getRandomLength } from "../utils"; @@ -11,7 +12,7 @@ export const SpreadsheetIssueRowLoader = (props: { columnCount: number }) => ( /> - {[...Array(props.columnCount)].map((_, colIndex) => ( + {range(props.columnCount).map((colIndex) => (
@@ -27,7 +28,7 @@ export const SpreadsheetLayoutLoader = () => ( - {[...Array(10)].map((_, index) => ( + {range(10).map((index) => ( ( - {[...Array(16)].map((_, rowIndex) => ( + {range(16).map((rowIndex) => ( ))} diff --git a/web/core/components/ui/loader/notification-loader.tsx b/web/core/components/ui/loader/notification-loader.tsx index 7485c2c4c..4526e400d 100644 --- a/web/core/components/ui/loader/notification-loader.tsx +++ b/web/core/components/ui/loader/notification-loader.tsx @@ -1,6 +1,8 @@ +import range from "lodash/range"; + export const NotificationsLoader = () => (
- {[...Array(3)].map((i) => ( + {range(3).map((i) => (
diff --git a/web/core/components/ui/loader/pages-loader.tsx b/web/core/components/ui/loader/pages-loader.tsx index 612c17d88..296c87275 100644 --- a/web/core/components/ui/loader/pages-loader.tsx +++ b/web/core/components/ui/loader/pages-loader.tsx @@ -1,15 +1,17 @@ +import range from "lodash/range"; + export const PagesLoader = () => (

Pages

- {[...Array(5)].map((i) => ( + {range(5).map((i) => ( ))}
- {[...Array(5)].map((i) => ( + {range(5).map((i) => (
diff --git a/web/core/components/ui/loader/projects-loader.tsx b/web/core/components/ui/loader/projects-loader.tsx index d1a781d6b..263919cd1 100644 --- a/web/core/components/ui/loader/projects-loader.tsx +++ b/web/core/components/ui/loader/projects-loader.tsx @@ -1,7 +1,9 @@ +import range from "lodash/range"; + export const ProjectsLoader = () => (
- {[...Array(3)].map((i) => ( + {range(3).map((i) => (
(
- {[...Array(10)].map((i) => ( + {range(10).map((i) => (
diff --git a/web/core/components/ui/loader/settings/api-token.tsx b/web/core/components/ui/loader/settings/api-token.tsx index e31090bff..ca8f59cda 100644 --- a/web/core/components/ui/loader/settings/api-token.tsx +++ b/web/core/components/ui/loader/settings/api-token.tsx @@ -1,3 +1,5 @@ +import range from "lodash/range"; + export const APITokenSettingsLoader = () => (
@@ -5,7 +7,7 @@ export const APITokenSettingsLoader = () => (
- {[...Array(2)].map((i) => ( + {range(2).map((i) => (
diff --git a/web/core/components/ui/loader/settings/email.tsx b/web/core/components/ui/loader/settings/email.tsx index 87634bf09..964a68b08 100644 --- a/web/core/components/ui/loader/settings/email.tsx +++ b/web/core/components/ui/loader/settings/email.tsx @@ -1,3 +1,5 @@ +import range from "lodash/range"; + export const EmailSettingsLoader = () => (
@@ -8,7 +10,7 @@ export const EmailSettingsLoader = () => (
- {[...Array(4)].map((i) => ( + {range(4).map((i) => (
diff --git a/web/core/components/ui/loader/settings/import-and-export.tsx b/web/core/components/ui/loader/settings/import-and-export.tsx index a3561207d..eea44c493 100644 --- a/web/core/components/ui/loader/settings/import-and-export.tsx +++ b/web/core/components/ui/loader/settings/import-and-export.tsx @@ -1,6 +1,8 @@ +import range from "lodash/range"; + export const ImportExportSettingsLoader = () => (
- {[...Array(2)].map((i) => ( + {range(2).map((i) => (
diff --git a/web/core/components/ui/loader/settings/integration.tsx b/web/core/components/ui/loader/settings/integration.tsx index 2260517ee..ed4253760 100644 --- a/web/core/components/ui/loader/settings/integration.tsx +++ b/web/core/components/ui/loader/settings/integration.tsx @@ -1,6 +1,8 @@ +import range from "lodash/range"; + export const IntegrationsSettingsLoader = () => (
- {[...Array(2)].map((i) => ( + {range(2).map((i) => (
(
- {[...Array(4)].map((i) => ( + {range(4).map((i) => (
diff --git a/web/core/components/ui/loader/view-list-loader.tsx b/web/core/components/ui/loader/view-list-loader.tsx index 8b59b57a2..71432a543 100644 --- a/web/core/components/ui/loader/view-list-loader.tsx +++ b/web/core/components/ui/loader/view-list-loader.tsx @@ -1,6 +1,8 @@ +import range from "lodash/range"; + export const ViewListLoader = () => (
- {[...Array(8)].map((i) => ( + {range(8).map((i) => (
diff --git a/web/core/components/web-hooks/form/secret-key.tsx b/web/core/components/web-hooks/form/secret-key.tsx index cbe5ea799..62815a6e2 100644 --- a/web/core/components/web-hooks/form/secret-key.tsx +++ b/web/core/components/web-hooks/form/secret-key.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, FC } from "react"; +import range from "lodash/range"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // icons @@ -102,7 +103,7 @@ export const WebhookSecretKey: FC = observer((props) => {

{webhookSecretKey}

) : (
- {[...Array(30)].map((_, index) => ( + {range(30).map((index) => (
))}
diff --git a/web/core/components/workspace-notifications/sidebar/loader.tsx b/web/core/components/workspace-notifications/sidebar/loader.tsx index 9889b221d..6feca28cc 100644 --- a/web/core/components/workspace-notifications/sidebar/loader.tsx +++ b/web/core/components/workspace-notifications/sidebar/loader.tsx @@ -1,6 +1,8 @@ +import range from "lodash/range"; + export const NotificationsLoader = () => (
- {[...Array(8)].map((i) => ( + {range(8).map((i) => (
From 63bc01f38524aa4078db6dc4976034935fdf7d19 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:35:09 +0530 Subject: [PATCH 05/52] [WEB-2774]fix:reordering favorites and favorite folders (#6119) * fixed re order for favorites * fixed lint errors * added reorder * fixed reorder inside folder * fixed lint issues * memoized reorder * removed unnecessary comments * seprated duplicate logic to a common file * removed code comments * fixed favorite remove while reorder inside folder * fixed folder remove while reorder inside folder * fixed-reorder issue * added last child to drop handled * fixed orderby function * removed unncessasary comments --- .../sidebar/favorites/favorite-folder.tsx | 1 - .../sidebar/favorites/favorites-menu.tsx | 17 +++----- .../sidebar/favorites/favorites.helpers.ts | 41 ------------------- web/core/store/favorite.store.ts | 41 +++++++++++++++---- 4 files changed, 40 insertions(+), 60 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 5956c8d2e..f7bad0400 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -34,7 +34,6 @@ type Props = { favorite: IFavorite; handleRemoveFromFavorites: (favorite: IFavorite) => void; handleRemoveFromFavoritesFolder: (favoriteId: string) => void; - handleReorder: (favoriteId: string, sequence: number) => void; handleDrop: (self: DropTargetRecord,source: ElementDragPayload, location: DragLocationHistory) => void; }; diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 024f2cdf8..8aea968ec 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -28,7 +28,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FavoriteFolder } from "./favorite-folder"; import { FavoriteRoot } from "./favorite-items"; -import { getDestinationStateSequence, getInstructionFromPayload, TargetData } from "./favorites.helpers"; +import { getInstructionFromPayload, TargetData } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; export const SidebarFavoritesMenu = observer(() => { @@ -94,22 +94,20 @@ export const SidebarFavoritesMenu = observer(() => { handleMoveToFolder(sourceData.id, parentId); } //handle remove from folder if dropped outside of the folder - if (parentId && sourceData.isChild) { + if (parentId && parentId !== sourceData.parentId && sourceData.isChild) { handleRemoveFromFavoritesFolder(sourceData.id); } // handle reordering at root level if (droppedFavId) { if (instruction != "make-child") { - const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); - handleReorder(sourceData.id, destinationSequence || 0); + handleReorder(sourceData.id, droppedFavId, instruction); } } } else { //handling reordering for favorites if (droppedFavId) { - const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); - handleReorder(sourceData.id, destinationSequence || 0); + handleReorder(sourceData.id, droppedFavId, instruction); } // handle removal from folder if dropped outside a folder @@ -147,10 +145,8 @@ export const SidebarFavoritesMenu = observer(() => { }; const handleReorder = useCallback( - (favoriteId: string, sequence: number) => { - reOrderFavorite(workspaceSlug.toString(), favoriteId, { - sequence: sequence, - }).catch(() => { + (favoriteId: string, droppedFavId: string, edge: string | undefined) => { + reOrderFavorite(workspaceSlug.toString(), favoriteId, droppedFavId, edge).catch(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", @@ -271,7 +267,6 @@ export const SidebarFavoritesMenu = observer(() => { isLastChild={index === length - 1} handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} - handleReorder={handleReorder} handleDrop={handleDrop} /> ) : ( diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index 94d54f894..a7364c9ed 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -9,47 +9,6 @@ export type TargetData = { isChild: boolean; } -export const getDestinationStateSequence = ( - favoriteMap: Record, - destinationId: string, - edge: string | undefined -) => { - const defaultSequence = 65535; - if (!edge) return defaultSequence; - - - const favoriteIds = orderBy(Object.values(favoriteMap), "sequence", "desc") - .filter((fav: IFavorite) => !fav.parent) - .map((fav: IFavorite) => fav.id); - const destinationStateIndex = favoriteIds.findIndex((id) => id === destinationId); - const destinationStateSequence = favoriteMap[destinationId]?.sequence || undefined; - - if (!destinationStateSequence) return defaultSequence; - - - let resultSequence = defaultSequence; - if (edge === "reorder-above") { - const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined; - - if (prevStateSequence === undefined) { - resultSequence = destinationStateSequence + defaultSequence; - }else { - resultSequence = (destinationStateSequence + prevStateSequence) / 2 - } - } else if (edge === "reorder-below") { - const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined; - - if (nextStateSequence === undefined) { - resultSequence = destinationStateSequence - defaultSequence; - } else { - resultSequence = (destinationStateSequence + nextStateSequence) / 2; - } - } - resultSequence = Math.round(resultSequence) - - return resultSequence; -}; - /** * extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload * @param dropTarget dropTarget for which the instruction is required diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index cc1ac1b2d..3f4f636d3 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -1,4 +1,4 @@ -import { uniqBy } from "lodash"; +import { orderBy, result, uniqBy } from "lodash"; import set from "lodash/set"; import { action, observable, makeObservable, runInAction, computed } from "mobx"; import { v4 as uuidv4 } from "uuid"; @@ -28,7 +28,12 @@ export interface IFavoriteStore { getGroupedFavorites: (workspaceSlug: string, favoriteId: string) => Promise; moveFavoriteToFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise; - reOrderFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; + reOrderFavorite: ( + workspaceSlug: string, + favoriteId: string, + destinationId: string, + edge: string | undefined + ) => Promise; removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string) => Promise; removeFavoriteFromStore: (entity_identifier: string) => void; } @@ -190,14 +195,37 @@ export class FavoriteStore implements IFavoriteStore { } }; - reOrderFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial) => { + reOrderFavorite = async ( + workspaceSlug: string, + favoriteId: string, + destinationId: string, + edge: string | undefined + ) => { const initialSequence = this.favoriteMap[favoriteId].sequence; try { + let resultSequence = 10000; + if (edge) { + const sortedIds = orderBy(Object.values(this.favoriteMap), "sequence", "desc").map((fav: IFavorite) => fav.id); + const destinationSequence = this.favoriteMap[destinationId]?.sequence || undefined; + if (destinationSequence) { + const destinationIndex = sortedIds.findIndex((id) => id === destinationId); + if (edge === "reorder-above") { + const prevSequence = this.favoriteMap[sortedIds[destinationIndex - 1]]?.sequence || undefined; + if (prevSequence) { + resultSequence = (destinationSequence + prevSequence) / 2; + } else { + resultSequence = destinationSequence + resultSequence; + } + } else { + resultSequence = destinationSequence - resultSequence; + } + } + } runInAction(() => { - set(this.favoriteMap, [favoriteId, "sequence"], data.sequence); + set(this.favoriteMap, [favoriteId, "sequence"], resultSequence); }); - await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data); + await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { sequence: resultSequence }); } catch (error) { console.error("Failed to move favorite folder"); runInAction(() => { @@ -214,8 +242,7 @@ export class FavoriteStore implements IFavoriteStore { //remove parent set(this.favoriteMap, [favoriteId, "parent"], null); }); - await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null}); - + await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null }); } catch (error) { console.error("Failed to move favorite"); runInAction(() => { From 5150c661ab577c322f85b00efcdd8926ff679cb8 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:35:40 +0530 Subject: [PATCH 06/52] reduced the components moved (#6110) --- .../(projects)/notifications/layout.tsx | 2 +- .../components/workspace-notifications/index.ts | 1 + .../components/workspace-notifications}/root.tsx | 15 ++++++++++++--- .../workspace-notifications/sidebar/index.ts | 2 -- .../components/workspace-notifications/root.tsx | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 web/ce/components/workspace-notifications/index.ts rename web/{core/components/workspace-notifications/sidebar => ce/components/workspace-notifications}/root.tsx (89%) create mode 100644 web/ee/components/workspace-notifications/root.tsx diff --git a/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx b/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx index b8b80b4d2..b69da040a 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/layout.tsx @@ -1,7 +1,7 @@ "use client"; // components -import { NotificationsSidebar } from "@/components/workspace-notifications"; +import { NotificationsSidebar } from "@/plane-web/components/workspace-notifications"; export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/web/ce/components/workspace-notifications/index.ts b/web/ce/components/workspace-notifications/index.ts new file mode 100644 index 000000000..c8711b96a --- /dev/null +++ b/web/ce/components/workspace-notifications/index.ts @@ -0,0 +1 @@ +export * from './root' \ No newline at end of file diff --git a/web/core/components/workspace-notifications/sidebar/root.tsx b/web/ce/components/workspace-notifications/root.tsx similarity index 89% rename from web/core/components/workspace-notifications/sidebar/root.tsx rename to web/ce/components/workspace-notifications/root.tsx index 767b96f42..97083d8cb 100644 --- a/web/core/components/workspace-notifications/sidebar/root.tsx +++ b/web/ce/components/workspace-notifications/root.tsx @@ -1,6 +1,6 @@ "use client"; -import { FC } from "react"; +import { FC, useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components @@ -14,7 +14,7 @@ import { NotificationCardListRoot, } from "@/components/workspace-notifications"; // constants -import { NOTIFICATION_TABS } from "@/constants/notification"; +import { NOTIFICATION_TABS, TNotificationTab } from "@/constants/notification"; // helpers import { cn } from "@/helpers/common.helper"; import { getNumberCount } from "@/helpers/string.helper"; @@ -37,6 +37,15 @@ export const NotificationsSidebar: FC = observer(() => { const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined; const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined; + const handleTabClick = useCallback( + (tabValue: TNotificationTab) => { + if (currentNotificationTab !== tabValue) { + setCurrentNotificationTab(tabValue); + } + }, + [currentNotificationTab, setCurrentNotificationTab] + ); + if (!workspaceSlug || !workspace) return <>; return ( @@ -56,7 +65,7 @@ export const NotificationsSidebar: FC = observer(() => {
currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)} + onClick={()=>handleTabClick(tab.value)} >
Date: Mon, 2 Dec 2024 13:36:12 +0530 Subject: [PATCH 07/52] fix: escape markdown content for images (#6096) --- .../core/extensions/custom-image/custom-image.ts | 13 +++---------- .../custom-image/read-only-custom-image.ts | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-image/custom-image.ts b/packages/editor/src/core/extensions/custom-image/custom-image.ts index a232bb258..3b64db8d0 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -1,11 +1,9 @@ import { Editor, mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; -import { MarkdownSerializerState } from "@tiptap/pm/markdown"; -import { Node } from "@tiptap/pm/model"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; // extensions -import { CustomImageNode, ImageAttributes } from "@/extensions/custom-image"; +import { CustomImageNode } from "@/extensions/custom-image"; // plugins import { TrackImageDeletionPlugin, TrackImageRestorationPlugin, isFileValid } from "@/plugins/image"; // types @@ -126,14 +124,9 @@ export const CustomImageExtension = (props: TFileHandler) => { deletedImageSet: new Map(), uploadInProgress: false, maxFileSize, + // escape markdown for images markdown: { - serialize(state: MarkdownSerializerState, node: Node) { - const attrs = node.attrs as ImageAttributes; - const imageSource = state.esc(this?.editor?.commands?.getImageSource?.(attrs.src) || attrs.src); - const imageWidth = state.esc(attrs.width?.toString()); - state.write(``); - state.closeBlock(node); - }, + serialize() {}, }, }; }, diff --git a/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts b/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts index 3248329f0..c27970d92 100644 --- a/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts @@ -1,10 +1,8 @@ import { mergeAttributes } from "@tiptap/core"; import { Image } from "@tiptap/extension-image"; -import { MarkdownSerializerState } from "@tiptap/pm/markdown"; -import { Node } from "@tiptap/pm/model"; import { ReactNodeViewRenderer } from "@tiptap/react"; // components -import { CustomImageNode, ImageAttributes, UploadImageExtensionStorage } from "@/extensions/custom-image"; +import { CustomImageNode, UploadImageExtensionStorage } from "@/extensions/custom-image"; // types import { TFileHandler } from "@/types"; @@ -54,14 +52,9 @@ export const CustomReadOnlyImageExtension = (props: Pick`); - state.closeBlock(node); - }, + serialize() {}, }, }; }, From 11bfbe560a89c71a76ccbba0c228731359dbc5e3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:47:50 +0530 Subject: [PATCH 08/52] fix: checked colored todo list item (#6113) --- packages/editor/src/styles/editor.css | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/styles/editor.css b/packages/editor/src/styles/editor.css index fff3b533e..8c2210600 100644 --- a/packages/editor/src/styles/editor.css +++ b/packages/editor/src/styles/editor.css @@ -179,13 +179,26 @@ ul[data-type="taskList"] li > label input[type="checkbox"] { } } -ul[data-type="taskList"] li > div > p { - margin-top: 10px; +ul[data-type="taskList"] li > div { + & > p { + margin-top: 10px; + transition: color 0.2s ease; + } + + [data-text-color] { + transition: opacity 0.2s ease; + } } -ul[data-type="taskList"] li[data-checked="true"] > div > p { - color: rgb(var(--color-text-400)); - transition: color 0.2s ease; +ul[data-type="taskList"] li[data-checked="true"] { + & > div > p { + color: rgb(var(--color-text-400)); + } + + [data-text-color] { + opacity: 0.6; + transition: opacity 0.2s ease; + } } /* end to-do list */ From 9f14167ef517ae2466c1f5d1e9a37ec7915eac89 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:51:27 +0530 Subject: [PATCH 09/52] refactor: editor code splitting (#6102) * fix: merge conflicts resolved from preview * fix: space app build errors * fix: product updates modal * fix: build errors * fix: lite text read only editor * refactor: additional options push logic --- .../src/ce/extensions/core/extensions.ts | 12 +++++++ .../editor/src/ce/extensions/core/index.ts | 2 ++ .../extensions/core/read-only-extensions.ts | 12 +++++++ .../src/ce/extensions/core/without-props.ts | 3 ++ .../src/ce/extensions/document-extensions.tsx | 8 ++++- packages/editor/src/ce/extensions/index.ts | 2 ++ .../src/ce/extensions/slash-commands.tsx | 14 ++++++++ .../collaborative-read-only-editor.tsx | 2 ++ .../editors/document/read-only-editor.tsx | 5 ++- .../components/editors/editor-wrapper.tsx | 2 ++ .../editors/read-only-editor-wrapper.tsx | 2 ++ .../components/editors/rich-text/editor.tsx | 13 ++++--- .../src/core/extensions/core-without-props.ts | 3 ++ .../editor/src/core/extensions/extensions.tsx | 13 +++++-- .../core/extensions/read-only-extensions.tsx | 13 +++++-- .../slash-commands/command-items-list.tsx | 35 +++++++++++++++---- .../core/extensions/slash-commands/root.tsx | 16 +++++++-- .../core/hooks/use-collaborative-editor.ts | 1 + packages/editor/src/core/hooks/use-editor.ts | 12 ++++++- .../use-read-only-collaborative-editor.ts | 2 ++ .../src/core/hooks/use-read-only-editor.ts | 11 +++--- .../editor/src/core/types/collaboration.ts | 2 +- packages/editor/src/core/types/editor.ts | 3 +- packages/editor/src/core/types/extensions.ts | 2 +- .../core/types/slash-commands-suggestion.ts | 2 ++ .../components/editor/lite-text-editor.tsx | 4 ++- .../editor/lite-text-read-only-editor.tsx | 6 +++- .../components/editor/rich-text-editor.tsx | 6 ++-- .../editor/rich-text-read-only-editor.tsx | 6 +++- web/ce/hooks/use-editor-flagging.ts | 6 +++- .../lite-text-editor/lite-text-editor.tsx | 7 +++- .../lite-text-read-only-editor.tsx | 10 +++++- .../rich-text-editor/rich-text-editor.tsx | 7 +++- .../rich-text-read-only-editor.tsx | 10 +++++- .../global/product-updates/modal.tsx | 1 + .../components/pages/editor/editor-body.tsx | 5 +-- web/core/components/pages/version/editor.tsx | 4 +++ 37 files changed, 218 insertions(+), 46 deletions(-) create mode 100644 packages/editor/src/ce/extensions/core/extensions.ts create mode 100644 packages/editor/src/ce/extensions/core/index.ts create mode 100644 packages/editor/src/ce/extensions/core/read-only-extensions.ts create mode 100644 packages/editor/src/ce/extensions/core/without-props.ts create mode 100644 packages/editor/src/ce/extensions/slash-commands.tsx diff --git a/packages/editor/src/ce/extensions/core/extensions.ts b/packages/editor/src/ce/extensions/core/extensions.ts new file mode 100644 index 000000000..d03229133 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/extensions.ts @@ -0,0 +1,12 @@ +import { Extensions } from "@tiptap/core"; +// types +import { TExtensions } from "@/types"; + +type Props = { + disabledExtensions: TExtensions[]; +}; + +export const CoreEditorAdditionalExtensions = (props: Props): Extensions => { + const {} = props; + return []; +}; diff --git a/packages/editor/src/ce/extensions/core/index.ts b/packages/editor/src/ce/extensions/core/index.ts new file mode 100644 index 000000000..9ffc978c3 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/index.ts @@ -0,0 +1,2 @@ +export * from "./extensions"; +export * from "./read-only-extensions"; diff --git a/packages/editor/src/ce/extensions/core/read-only-extensions.ts b/packages/editor/src/ce/extensions/core/read-only-extensions.ts new file mode 100644 index 000000000..398848e31 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/read-only-extensions.ts @@ -0,0 +1,12 @@ +import { Extensions } from "@tiptap/core"; +// types +import { TExtensions } from "@/types"; + +type Props = { + disabledExtensions: TExtensions[]; +}; + +export const CoreReadOnlyEditorAdditionalExtensions = (props: Props): Extensions => { + const {} = props; + return []; +}; diff --git a/packages/editor/src/ce/extensions/core/without-props.ts b/packages/editor/src/ce/extensions/core/without-props.ts new file mode 100644 index 000000000..0debff0ea --- /dev/null +++ b/packages/editor/src/ce/extensions/core/without-props.ts @@ -0,0 +1,3 @@ +import { Extensions } from "@tiptap/core"; + +export const CoreEditorAdditionalExtensionsWithoutProps: Extensions = []; diff --git a/packages/editor/src/ce/extensions/document-extensions.tsx b/packages/editor/src/ce/extensions/document-extensions.tsx index e3c94fa0e..35d7c0f3d 100644 --- a/packages/editor/src/ce/extensions/document-extensions.tsx +++ b/packages/editor/src/ce/extensions/document-extensions.tsx @@ -15,7 +15,13 @@ type Props = { export const DocumentEditorAdditionalExtensions = (_props: Props) => { const { disabledExtensions } = _props; - const extensions: Extensions = disabledExtensions?.includes("slash-commands") ? [] : [SlashCommands()]; + const extensions: Extensions = disabledExtensions?.includes("slash-commands") + ? [] + : [ + SlashCommands({ + disabledExtensions, + }), + ]; return extensions; }; diff --git a/packages/editor/src/ce/extensions/index.ts b/packages/editor/src/ce/extensions/index.ts index 4a975b8c5..c9f58a936 100644 --- a/packages/editor/src/ce/extensions/index.ts +++ b/packages/editor/src/ce/extensions/index.ts @@ -1 +1,3 @@ +export * from "./core"; export * from "./document-extensions"; +export * from "./slash-commands"; diff --git a/packages/editor/src/ce/extensions/slash-commands.tsx b/packages/editor/src/ce/extensions/slash-commands.tsx new file mode 100644 index 000000000..6eabee082 --- /dev/null +++ b/packages/editor/src/ce/extensions/slash-commands.tsx @@ -0,0 +1,14 @@ +// extensions +import { TSlashCommandAdditionalOption } from "@/extensions"; +// types +import { TExtensions } from "@/types"; + +type Props = { + disabledExtensions: TExtensions[]; +}; + +export const coreEditorAdditionalSlashCommandOptions = (props: Props): TSlashCommandAdditionalOption[] => { + const {} = props; + const options: TSlashCommandAdditionalOption[] = []; + return options; +}; diff --git a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx index aa925abec..89acace7b 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx @@ -15,6 +15,7 @@ import { EditorReadOnlyRefApi, ICollaborativeDocumentReadOnlyEditor } from "@/ty const CollaborativeDocumentReadOnlyEditor = (props: ICollaborativeDocumentReadOnlyEditor) => { const { containerClassName, + disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, editorClassName = "", embedHandler, @@ -37,6 +38,7 @@ const CollaborativeDocumentReadOnlyEditor = (props: ICollaborativeDocumentReadOn } const { editor, hasServerConnectionFailed, hasServerSynced } = useReadOnlyCollaborativeEditor({ + disabledExtensions, editorClassName, extensions, fileHandler, diff --git a/packages/editor/src/core/components/editors/document/read-only-editor.tsx b/packages/editor/src/core/components/editors/document/read-only-editor.tsx index 8544157aa..b36fb44a7 100644 --- a/packages/editor/src/core/components/editors/document/read-only-editor.tsx +++ b/packages/editor/src/core/components/editors/document/read-only-editor.tsx @@ -10,9 +10,10 @@ import { getEditorClassNames } from "@/helpers/common"; // hooks import { useReadOnlyEditor } from "@/hooks/use-read-only-editor"; // types -import { EditorReadOnlyRefApi, IMentionHighlight, TDisplayConfig, TFileHandler } from "@/types"; +import { EditorReadOnlyRefApi, IMentionHighlight, TDisplayConfig, TExtensions, TFileHandler } from "@/types"; interface IDocumentReadOnlyEditor { + disabledExtensions: TExtensions[]; id: string; initialValue: string; containerClassName: string; @@ -31,6 +32,7 @@ interface IDocumentReadOnlyEditor { const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => { const { containerClassName, + disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, editorClassName = "", embedHandler, @@ -51,6 +53,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => { } const editor = useReadOnlyEditor({ + disabledExtensions, editorClassName, extensions, fileHandler, diff --git a/packages/editor/src/core/components/editors/editor-wrapper.tsx b/packages/editor/src/core/components/editors/editor-wrapper.tsx index 33f011535..075420ed7 100644 --- a/packages/editor/src/core/components/editors/editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/editor-wrapper.tsx @@ -19,6 +19,7 @@ export const EditorWrapper: React.FC = (props) => { const { children, containerClassName, + disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, editorClassName = "", extensions, @@ -37,6 +38,7 @@ export const EditorWrapper: React.FC = (props) => { } = props; const editor = useEditor({ + disabledExtensions, editorClassName, enableHistory: true, extensions, diff --git a/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx b/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx index e06826a28..6cd360ac0 100644 --- a/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx +++ b/packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx @@ -12,6 +12,7 @@ import { IReadOnlyEditorProps } from "@/types"; export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => { const { containerClassName, + disabledExtensions, displayConfig = DEFAULT_DISPLAY_CONFIG, editorClassName = "", fileHandler, @@ -22,6 +23,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => { } = props; const editor = useReadOnlyEditor({ + disabledExtensions, editorClassName, fileHandler, forwardedRef, diff --git a/packages/editor/src/core/components/editors/rich-text/editor.tsx b/packages/editor/src/core/components/editors/rich-text/editor.tsx index 87dba8b4d..ffcc21da6 100644 --- a/packages/editor/src/core/components/editors/rich-text/editor.tsx +++ b/packages/editor/src/core/components/editors/rich-text/editor.tsx @@ -8,12 +8,7 @@ import { SideMenuExtension, SlashCommands } from "@/extensions"; import { EditorRefApi, IRichTextEditor } from "@/types"; const RichTextEditor = (props: IRichTextEditor) => { - const { - disabledExtensions, - dragDropEnabled, - bubbleMenuEnabled = true, - extensions: externalExtensions = [], - } = props; + const { disabledExtensions, dragDropEnabled, bubbleMenuEnabled = true, extensions: externalExtensions = [] } = props; const getExtensions = useCallback(() => { const extensions = [ @@ -24,7 +19,11 @@ const RichTextEditor = (props: IRichTextEditor) => { }), ]; if (!disabledExtensions?.includes("slash-commands")) { - extensions.push(SlashCommands()); + extensions.push( + SlashCommands({ + disabledExtensions, + }) + ); } return extensions; diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts index 075d90f2d..cb1b0a002 100644 --- a/packages/editor/src/core/extensions/core-without-props.ts +++ b/packages/editor/src/core/extensions/core-without-props.ts @@ -19,6 +19,8 @@ import { TableHeader, TableCell, TableRow, Table } from "./table"; import { CustomTextAlignExtension } from "./text-align"; import { CustomCalloutExtensionConfig } from "./callout/extension-config"; import { CustomColorExtension } from "./custom-color"; +// plane editor extensions +import { CoreEditorAdditionalExtensionsWithoutProps } from "@/plane-editor/extensions/core/without-props"; export const CoreEditorExtensionsWithoutProps = [ StarterKit.configure({ @@ -89,6 +91,7 @@ export const CoreEditorExtensionsWithoutProps = [ CustomTextAlignExtension, CustomCalloutExtensionConfig, CustomColorExtension, + ...CoreEditorAdditionalExtensionsWithoutProps, ]; export const DocumentEditorExtensionsWithoutProps = [IssueWidgetWithoutProps()]; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 959d20e2b..b8910a56c 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import CharacterCount from "@tiptap/extension-character-count"; import Placeholder from "@tiptap/extension-placeholder"; import TaskItem from "@tiptap/extension-task-item"; @@ -32,9 +33,12 @@ import { // helpers import { isValidHttpUrl } from "@/helpers/common"; // types -import { IMentionHighlight, IMentionSuggestion, TFileHandler } from "@/types"; +import { IMentionHighlight, IMentionSuggestion, TExtensions, TFileHandler } from "@/types"; +// plane editor extensions +import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions"; type TArguments = { + disabledExtensions: TExtensions[]; enableHistory: boolean; fileHandler: TFileHandler; mentionConfig: { @@ -45,8 +49,8 @@ type TArguments = { tabIndex?: number; }; -export const CoreEditorExtensions = (args: TArguments) => { - const { enableHistory, fileHandler, mentionConfig, placeholder, tabIndex } = args; +export const CoreEditorExtensions = (args: TArguments): Extensions => { + const { disabledExtensions, enableHistory, fileHandler, mentionConfig, placeholder, tabIndex } = args; return [ StarterKit.configure({ @@ -162,5 +166,8 @@ export const CoreEditorExtensions = (args: TArguments) => { CustomTextAlignExtension, CustomCalloutExtension, CustomColorExtension, + ...CoreEditorAdditionalExtensions({ + disabledExtensions, + }), ]; }; diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index 2d90592d6..9ca99495e 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import CharacterCount from "@tiptap/extension-character-count"; import TaskItem from "@tiptap/extension-task-item"; import TaskList from "@tiptap/extension-task-list"; @@ -28,17 +29,20 @@ import { // helpers import { isValidHttpUrl } from "@/helpers/common"; // types -import { IMentionHighlight, TFileHandler } from "@/types"; +import { IMentionHighlight, TExtensions, TFileHandler } from "@/types"; +// plane editor extensions +import { CoreReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions"; type Props = { + disabledExtensions: TExtensions[]; fileHandler: Pick; mentionConfig: { mentionHighlights?: () => Promise; }; }; -export const CoreReadOnlyEditorExtensions = (props: Props) => { - const { fileHandler, mentionConfig } = props; +export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { + const { disabledExtensions, fileHandler, mentionConfig } = props; return [ StarterKit.configure({ @@ -128,5 +132,8 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => { HeadingListExtension, CustomTextAlignExtension, CustomCalloutReadOnlyExtension, + ...CoreReadOnlyEditorAdditionalExtensions({ + disabledExtensions, + }), ]; }; diff --git a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx index c19bda306..1efb72901 100644 --- a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx +++ b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx @@ -39,17 +39,27 @@ import { setText, } from "@/helpers/editor-commands"; // types -import { CommandProps, ISlashCommandItem } from "@/types"; +import { CommandProps, ISlashCommandItem, TExtensions, TSlashCommandSectionKeys } from "@/types"; +// plane editor extensions +import { coreEditorAdditionalSlashCommandOptions } from "@/plane-editor/extensions"; +// local types +import { TSlashCommandAdditionalOption } from "./root"; export type TSlashCommandSection = { - key: string; + key: TSlashCommandSectionKeys; title?: string; items: ISlashCommandItem[]; }; +type TArgs = { + additionalOptions?: TSlashCommandAdditionalOption[]; + disabledExtensions: TExtensions[]; +}; + export const getSlashCommandFilteredSections = - (additionalOptions?: ISlashCommandItem[]) => + (args: TArgs) => ({ query }: { query: string }): TSlashCommandSection[] => { + const { additionalOptions, disabledExtensions } = args; const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [ { key: "general", @@ -201,7 +211,7 @@ export const getSlashCommandFilteredSections = ], }, { - key: "text-color", + key: "text-colors", title: "Colors", items: [ { @@ -242,7 +252,7 @@ export const getSlashCommandFilteredSections = ], }, { - key: "background-color", + key: "background-colors", title: "Background colors", items: [ { @@ -279,8 +289,19 @@ export const getSlashCommandFilteredSections = }, ]; - additionalOptions?.map((item) => { - SLASH_COMMAND_SECTIONS?.[0]?.items.push(item); + [ + ...(additionalOptions ?? []), + ...coreEditorAdditionalSlashCommandOptions({ + disabledExtensions, + }), + ]?.forEach((item) => { + const sectionToPushTo = SLASH_COMMAND_SECTIONS.find((s) => s.key === item.section) ?? SLASH_COMMAND_SECTIONS[0]; + const itemIndexToPushAfter = sectionToPushTo.items.findIndex((i) => i.commandKey === item.pushAfter); + if (itemIndexToPushAfter !== -1) { + sectionToPushTo.items.splice(itemIndexToPushAfter + 1, 0, item); + } else { + sectionToPushTo.items.push(item); + } }); const filteredSlashSections = SLASH_COMMAND_SECTIONS.map((section) => ({ diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index a99cbc5f9..62c353f92 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -3,7 +3,7 @@ import { ReactRenderer } from "@tiptap/react"; import Suggestion, { SuggestionOptions } from "@tiptap/suggestion"; import tippy from "tippy.js"; // types -import { ISlashCommandItem } from "@/types"; +import { ISlashCommandItem, TEditorCommands, TExtensions, TSlashCommandSectionKeys } from "@/types"; // components import { getSlashCommandFilteredSections } from "./command-items-list"; import { SlashCommandsMenu, SlashCommandsMenuProps } from "./command-menu"; @@ -12,6 +12,11 @@ export type SlashCommandOptions = { suggestion: Omit; }; +export type TSlashCommandAdditionalOption = ISlashCommandItem & { + section: TSlashCommandSectionKeys; + pushAfter: TEditorCommands; +}; + const Command = Extension.create({ name: "slash-command", addOptions() { @@ -102,10 +107,15 @@ const renderItems = () => { }; }; -export const SlashCommands = (additionalOptions?: ISlashCommandItem[]) => +type TExtensionProps = { + additionalOptions?: TSlashCommandAdditionalOption[]; + disabledExtensions: TExtensions[]; +}; + +export const SlashCommands = (props: TExtensionProps) => Command.configure({ suggestion: { - items: getSlashCommandFilteredSections(additionalOptions), + items: getSlashCommandFilteredSections(props), render: renderItems, }, }); diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index 5bee8c0c3..f32d7f4cc 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -75,6 +75,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { }, [provider, id]); const editor = useEditor({ + disabledExtensions, id, onTransaction, editorProps, diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts index eef72797c..5cddc79e5 100644 --- a/packages/editor/src/core/hooks/use-editor.ts +++ b/packages/editor/src/core/hooks/use-editor.ts @@ -16,12 +16,20 @@ import { IMarking, scrollSummary, scrollToNodeViaDOMCoordinates } from "@/helper // props import { CoreEditorProps } from "@/props"; // types -import { EditorRefApi, IMentionHighlight, IMentionSuggestion, TEditorCommands, TFileHandler } from "@/types"; +import { + EditorRefApi, + IMentionHighlight, + IMentionSuggestion, + TEditorCommands, + TExtensions, + TFileHandler, +} from "@/types"; export interface CustomEditorProps { editorClassName: string; editorProps?: EditorProps; enableHistory: boolean; + disabledExtensions: TExtensions[]; extensions?: any; fileHandler: TFileHandler; forwardedRef?: MutableRefObject; @@ -45,6 +53,7 @@ export interface CustomEditorProps { export const useEditor = (props: CustomEditorProps) => { const { + disabledExtensions, editorClassName, editorProps = {}, enableHistory, @@ -79,6 +88,7 @@ export const useEditor = (props: CustomEditorProps) => { }, extensions: [ ...CoreEditorExtensions({ + disabledExtensions, enableHistory, fileHandler, mentionConfig: { 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 d40819229..62e08e5d3 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 @@ -11,6 +11,7 @@ import { TReadOnlyCollaborativeEditorProps } from "@/types"; export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEditorProps) => { const { + disabledExtensions, editorClassName, editorProps = {}, extensions, @@ -66,6 +67,7 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit }, [provider, id]); const editor = useReadOnlyEditor({ + disabledExtensions, editorProps, editorClassName, extensions: [ diff --git a/packages/editor/src/core/hooks/use-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-editor.ts index 23ce023ad..f75fa7268 100644 --- a/packages/editor/src/core/hooks/use-read-only-editor.ts +++ b/packages/editor/src/core/hooks/use-read-only-editor.ts @@ -11,14 +11,15 @@ import { IMarking, scrollSummary } from "@/helpers/scroll-to-node"; // props import { CoreReadOnlyEditorProps } from "@/props"; // types -import { EditorReadOnlyRefApi, IMentionHighlight, TFileHandler } from "@/types"; +import { EditorReadOnlyRefApi, IMentionHighlight, TExtensions, TFileHandler } from "@/types"; interface CustomReadOnlyEditorProps { - initialValue?: string; + disabledExtensions: TExtensions[]; editorClassName: string; - forwardedRef?: MutableRefObject; - extensions?: any; editorProps?: EditorProps; + extensions?: any; + forwardedRef?: MutableRefObject; + initialValue?: string; fileHandler: Pick; handleEditorReady?: (value: boolean) => void; mentionHandler: { @@ -29,6 +30,7 @@ interface CustomReadOnlyEditorProps { export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => { const { + disabledExtensions, initialValue, editorClassName, forwardedRef, @@ -54,6 +56,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => { }, extensions: [ ...CoreReadOnlyEditorExtensions({ + disabledExtensions, mentionConfig: { mentionHighlights: mentionHandler.highlights, }, diff --git a/packages/editor/src/core/types/collaboration.ts b/packages/editor/src/core/types/collaboration.ts index 8609995ed..35fbdb996 100644 --- a/packages/editor/src/core/types/collaboration.ts +++ b/packages/editor/src/core/types/collaboration.ts @@ -20,7 +20,7 @@ export type TServerHandler = { }; type TCollaborativeEditorHookProps = { - disabledExtensions?: TExtensions[]; + disabledExtensions: TExtensions[]; editorClassName: string; editorProps?: EditorProps; extensions?: Extensions; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 53aae1f26..4b134a854 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -104,7 +104,7 @@ export interface EditorRefApi extends EditorReadOnlyRefApi { export interface IEditorProps { containerClassName?: string; displayConfig?: TDisplayConfig; - disabledExtensions?: TExtensions[]; + disabledExtensions: TExtensions[]; editorClassName?: string; fileHandler: TFileHandler; forwardedRef?: React.MutableRefObject; @@ -146,6 +146,7 @@ export interface ICollaborativeDocumentEditor // read only editor props export interface IReadOnlyEditorProps { containerClassName?: string; + disabledExtensions: TExtensions[]; displayConfig?: TDisplayConfig; editorClassName?: string; fileHandler: Pick; diff --git a/packages/editor/src/core/types/extensions.ts b/packages/editor/src/core/types/extensions.ts index 2be17a4ef..b3aacccc0 100644 --- a/packages/editor/src/core/types/extensions.ts +++ b/packages/editor/src/core/types/extensions.ts @@ -1 +1 @@ -export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands"| "enter-key"; +export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands" | "enter-key"; diff --git a/packages/editor/src/core/types/slash-commands-suggestion.ts b/packages/editor/src/core/types/slash-commands-suggestion.ts index 91c93203a..d6dfae076 100644 --- a/packages/editor/src/core/types/slash-commands-suggestion.ts +++ b/packages/editor/src/core/types/slash-commands-suggestion.ts @@ -8,6 +8,8 @@ export type CommandProps = { range: Range; }; +export type TSlashCommandSectionKeys = "general" | "text-colors" | "background-colors"; + export type ISlashCommandItem = { commandKey: TEditorCommands; key: string; diff --git a/space/core/components/editor/lite-text-editor.tsx b/space/core/components/editor/lite-text-editor.tsx index 0e3f34293..5d5027135 100644 --- a/space/core/components/editor/lite-text-editor.tsx +++ b/space/core/components/editor/lite-text-editor.tsx @@ -10,7 +10,8 @@ import { isCommentEmpty } from "@/helpers/string.helper"; // hooks import { useMention } from "@/hooks/use-mention"; -interface LiteTextEditorWrapperProps extends Omit { +interface LiteTextEditorWrapperProps + extends Omit { anchor: string; workspaceId: string; isSubmitting?: boolean; @@ -41,6 +42,7 @@ export const LiteTextEditor = React.forwardRef & { +type LiteTextReadOnlyEditorWrapperProps = Omit< + ILiteTextReadOnlyEditor, + "disabledExtensions" | "fileHandler" | "mentionHandler" +> & { anchor: string; }; @@ -18,6 +21,7 @@ export const LiteTextReadOnlyEditor = React.forwardRef { +interface RichTextEditorWrapperProps + extends Omit { uploadFile: (file: File) => Promise; } @@ -27,6 +26,7 @@ export const RichTextEditor = forwardRef & { +type RichTextReadOnlyEditorWrapperProps = Omit< + IRichTextReadOnlyEditor, + "disabledExtensions" | "fileHandler" | "mentionHandler" +> & { anchor: string; }; @@ -18,6 +21,7 @@ export const RichTextReadOnlyEditor = React.forwardRef ({ documentEditor: ["ai", "collaboration-cursor"], + liteTextEditor: ["ai", "collaboration-cursor"], richTextEditor: ["ai", "collaboration-cursor"], }); 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 0822f1a97..0fe75904d 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 @@ -14,9 +14,11 @@ import { isCommentEmpty } from "@/helpers/string.helper"; // hooks import { useMember, useMention, useUser } from "@/hooks/store"; // plane web hooks +import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; import { useFileSize } from "@/plane-web/hooks/use-file-size"; -interface LiteTextEditorWrapperProps extends Omit { +interface LiteTextEditorWrapperProps + extends Omit { workspaceSlug: string; workspaceId: string; projectId: string; @@ -49,6 +51,8 @@ export const LiteTextEditor = React.forwardRef getUserDetails(id) as IUserLite); @@ -72,6 +76,7 @@ export const LiteTextEditor = React.forwardRef & { +type LiteTextReadOnlyEditorWrapperProps = Omit< + ILiteTextReadOnlyEditor, + "disabledExtensions" | "fileHandler" | "mentionHandler" +> & { workspaceSlug: string; projectId: string; }; @@ -19,10 +24,13 @@ export const LiteTextReadOnlyEditor = React.forwardRef { +interface RichTextEditorWrapperProps + extends Omit { workspaceSlug: string; workspaceId: string; projectId: string; @@ -26,6 +28,8 @@ export const RichTextEditor = forwardRef getUserDetails(id) as IUserLite); @@ -42,6 +46,7 @@ export const RichTextEditor = forwardRef & { +type RichTextReadOnlyEditorWrapperProps = Omit< + IRichTextReadOnlyEditor, + "disabledExtensions" | "fileHandler" | "mentionHandler" +> & { workspaceSlug: string; projectId?: string; }; @@ -15,10 +20,13 @@ type RichTextReadOnlyEditorWrapperProps = Omit( ({ workspaceSlug, projectId, ...props }, ref) => { const { mentionHighlights } = useMention({}); + // editor flaggings + const { richTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString()); return ( = observer((props {data?.id && (

"} containerClassName="p-0 border-none" diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 0d299104f..ad27f9d7d 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -84,7 +84,7 @@ export const PageEditorBody: React.FC = observer((props) => { user: currentUser ?? undefined, }); // editor flaggings - const { documentEditor } = useEditorFlagging(); + const { documentEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString()); // page filters const { fontSize, fontStyle, isFullWidth } = usePageFilters(); // issue-embed @@ -224,7 +224,7 @@ export const PageEditorBody: React.FC = observer((props) => { realtimeConfig={realtimeConfig} serverHandler={serverHandler} user={userConfig} - disabledExtensions={documentEditor} + disabledExtensions={disabledExtensions} aiHandler={{ menu: getAIMenu, }} @@ -233,6 +233,7 @@ export const PageEditorBody: React.FC = observer((props) => { = observer((props getUserDetails, project: { getProjectMemberIds }, } = useMember(); + // editor flaggings + const { documentEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString() ?? ""); // derived values const projectMemberIds = projectId ? getProjectMemberIds(projectId.toString()) : []; const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite); @@ -101,6 +104,7 @@ export const PagesVersionEditor: React.FC = observer((props id={activeVersion ?? ""} initialValue={description ?? "

"} containerClassName="p-0 pb-64 border-none" + disabledExtensions={disabledExtensions} displayConfig={displayConfig} editorClassName="pl-10" fileHandler={getReadOnlyEditorFileHandlers({ From 8c04aa6f51700c29c935cad5f44c80814f1a6034 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:59:01 +0530 Subject: [PATCH 10/52] dev: revamp pages authorization (#6094) --- apiserver/plane/app/views/page/base.py | 16 +++++------ .../[projectId]/pages/(list)/header.tsx | 15 +++-------- web/core/store/pages/page.ts | 27 ++++++++++++------- web/core/store/pages/project-page.store.ts | 20 +++++++++++++- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index 24ceb2d3f..46ce81ce1 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -114,7 +114,7 @@ class PageViewSet(BaseViewSet): .distinct() ) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def create(self, request, slug, project_id): serializer = PageSerializer( data=request.data, @@ -134,7 +134,7 @@ class PageViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN, ROLE.MEMBER]) def partial_update(self, request, slug, project_id, pk): try: page = Page.objects.get( @@ -234,7 +234,7 @@ class PageViewSet(BaseViewSet): ) return Response(data, status=status.HTTP_200_OK) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def lock(self, request, slug, project_id, pk): page = Page.objects.filter( pk=pk, workspace__slug=slug, projects__id=project_id @@ -244,7 +244,7 @@ class PageViewSet(BaseViewSet): page.save() return Response(status=status.HTTP_204_NO_CONTENT) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def unlock(self, request, slug, project_id, pk): page = Page.objects.filter( pk=pk, workspace__slug=slug, projects__id=project_id @@ -255,7 +255,7 @@ class PageViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def access(self, request, slug, project_id, pk): access = request.data.get("access", 0) page = Page.objects.filter( @@ -296,7 +296,7 @@ class PageViewSet(BaseViewSet): pages = PageSerializer(queryset, many=True).data return Response(pages, status=status.HTTP_200_OK) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def archive(self, request, slug, project_id, pk): page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) @@ -323,7 +323,7 @@ class PageViewSet(BaseViewSet): return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def unarchive(self, request, slug, project_id, pk): page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) @@ -348,7 +348,7 @@ class PageViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) - @allow_permission([ROLE.ADMIN], creator=True, model=Page) + @allow_permission([ROLE.ADMIN], model=Page, creator=True) def destroy(self, request, slug, project_id, pk): page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx index e696a08f4..d3646b31b 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx @@ -13,9 +13,7 @@ import { BreadcrumbLink, Logo } from "@/components/common"; // constants import { EPageAccess } from "@/constants/page"; // hooks -import { useEventTracker, useProject, useProjectPages, useUserPermissions } from "@/hooks/store"; -// plane web hooks -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; +import { useEventTracker, useProject, useProjectPages } from "@/hooks/store"; export const PagesListHeader = observer(() => { // states @@ -26,16 +24,9 @@ export const PagesListHeader = observer(() => { const searchParams = useSearchParams(); const pageType = searchParams.get("type"); // store hooks - const { allowPermissions } = useUserPermissions(); - const { currentProjectDetails, loader } = useProject(); - const { createPage } = useProjectPages(); + const { canCurrentUserCreatePage, createPage } = useProjectPages(); const { setTrackElement } = useEventTracker(); - // auth - const canUserCreatePage = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], - EUserPermissionsLevel.PROJECT - ); // handle page create const handleCreatePage = async () => { setIsCreatingPage(true); @@ -87,7 +78,7 @@ export const PagesListHeader = observer(() => {
- {canUserCreatePage ? ( + {canCurrentUserCreatePage ? (