diff --git a/web/core/components/pages/editor/page-root.tsx b/web/core/components/pages/editor/page-root.tsx
index bb4bcb759b9..91c3107ad41 100644
--- a/web/core/components/pages/editor/page-root.tsx
+++ b/web/core/components/pages/editor/page-root.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
// editor
@@ -18,8 +18,6 @@ import {
import { useAppRouter } from "@/hooks/use-app-router";
import { usePageFallback } from "@/hooks/use-page-fallback";
import { useQueryParams } from "@/hooks/use-query-params";
-// plane web hooks
-import { EPageStoreType } from "@/plane-web/hooks/store";
// store
import { TPageInstance } from "@/store/pages/base-page";
@@ -38,13 +36,12 @@ type TPageRootProps = {
config: TPageRootConfig;
handlers: TPageRootHandlers;
page: TPageInstance;
- storeType: EPageStoreType;
webhookConnectionParams: TWebhookConnectionQueryParams;
workspaceSlug: string;
};
export const PageRoot = observer((props: TPageRootProps) => {
- const { config, handlers, page, storeType, webhookConnectionParams, workspaceSlug } = props;
+ const { config, handlers, page, webhookConnectionParams, workspaceSlug } = props;
// states
const [editorReady, setEditorReady] = useState(false);
const [hasConnectionFailed, setHasConnectionFailed] = useState(false);
@@ -56,7 +53,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
// search params
const searchParams = useSearchParams();
// derived values
- const { isContentEditable } = page;
+ const { isContentEditable, setEditorRef } = page;
// page fallback
usePageFallback({
editorRef,
@@ -67,6 +64,16 @@ export const PageRoot = observer((props: TPageRootProps) => {
// update query params
const { updateQueryParams } = useQueryParams();
+ const handleEditorReady = useCallback(
+ (status: boolean) => {
+ setEditorReady(status);
+ if (editorRef.current && !page.editorRef) {
+ setEditorRef(editorRef.current);
+ }
+ },
+ [page.editorRef, setEditorRef]
+ );
+
const version = searchParams.get("version");
useEffect(() => {
if (!version) {
@@ -89,6 +96,14 @@ export const PageRoot = observer((props: TPageRootProps) => {
};
const currentVersionDescription = editorRef.current?.getDocument().html;
+ // reset editor ref on unmount
+ useEffect(
+ () => () => {
+ setEditorRef(null);
+ },
+ [setEditorRef]
+ );
+
return (
<>
{
pageId={page.id ?? ""}
restoreEnabled={isContentEditable}
/>
-
+
= (props) => {
};
}, [editorRef]);
- const handleOnClick = (marking: IMarking) => {
- editorRef?.scrollSummary(marking);
- if (setSidePeekVisible) setSidePeekVisible(false);
- };
+ const handleOnClick = useCallback(
+ (marking: IMarking) => {
+ editorRef?.scrollSummary(marking);
+ setSidePeekVisible?.(false);
+ },
+ [editorRef, setSidePeekVisible]
+ );
const HeadingComponent: {
[key: number]: React.FC<{ marking: IMarking; onClick: () => void }>;
diff --git a/web/core/components/pages/editor/title.tsx b/web/core/components/pages/editor/title.tsx
index f7f3822d3c0..5864ac5d9b7 100644
--- a/web/core/components/pages/editor/title.tsx
+++ b/web/core/components/pages/editor/title.tsx
@@ -13,7 +13,7 @@ import { getPageName } from "@/helpers/page.helper";
import { usePageFilters } from "@/hooks/use-page-filters";
type Props = {
- editorRef: React.RefObject;
+ editorRef: EditorRefApi | null;
readOnly: boolean;
title: string | undefined;
updateTitle: (title: string) => void;
@@ -53,7 +53,7 @@ export const PageEditorTitle: React.FC = observer((props) => {
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
- editorRef.current?.setFocusAtPosition(0);
+ editorRef?.setFocusAtPosition(0);
}
}}
value={title}
diff --git a/web/core/components/pages/editor/toolbar/extra-options.tsx b/web/core/components/pages/editor/toolbar/extra-options.tsx
deleted file mode 100644
index d9198b84f13..00000000000
--- a/web/core/components/pages/editor/toolbar/extra-options.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-"use client";
-
-import { observer } from "mobx-react";
-// constants
-import { IS_FAVORITE_MENU_OPEN } from "@plane/constants";
-// editor
-import { EditorRefApi } from "@plane/editor";
-// plane hooks
-import { useLocalStorage } from "@plane/hooks";
-// ui
-import { ArchiveIcon, FavoriteStar, setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
-// components
-import { LockedComponent } from "@/components/icons/locked-component";
-import { PageInfoPopover, PageOptionsDropdown } from "@/components/pages";
-// helpers
-import { renderFormattedDate } from "@/helpers/date-time.helper";
-// hooks
-import useOnlineStatus from "@/hooks/use-online-status";
-// plane web hooks
-import { EPageStoreType } from "@/plane-web/hooks/store";
-// store
-import { TPageInstance } from "@/store/pages/base-page";
-
-type Props = {
- editorRef: EditorRefApi;
- page: TPageInstance;
- storeType: EPageStoreType;
-};
-
-export const PageExtraOptions: React.FC = observer((props) => {
- const { editorRef, page, storeType } = props;
- // derived values
- const {
- archived_at,
- isContentEditable,
- is_favorite,
- is_locked,
- canCurrentUserFavoritePage,
- addToFavorites,
- removePageFromFavorites,
- } = page;
- // use online status
- const { isOnline } = useOnlineStatus();
- // local storage
- const { setValue: toggleFavoriteMenu, storedValue: isFavoriteMenuOpen } = useLocalStorage(
- IS_FAVORITE_MENU_OPEN,
- false
- );
- // favorite handler
- const handleFavorite = () => {
- if (is_favorite) {
- removePageFromFavorites().then(() =>
- setToast({
- type: TOAST_TYPE.SUCCESS,
- title: "Success!",
- message: "Page removed from favorites.",
- })
- );
- } else {
- addToFavorites().then(() => {
- if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
- setToast({
- type: TOAST_TYPE.SUCCESS,
- title: "Success!",
- message: "Page added to favorites.",
- });
- });
- }
- };
-
- return (
-
- {is_locked &&
}
- {archived_at && (
-
-
-
Archived at {renderFormattedDate(archived_at)}
-
- )}
- {isContentEditable && !isOnline && (
-
-
-
- Offline
-
-
- )}
- {canCurrentUserFavoritePage && (
-
- )}
-
-
-
- );
-});
diff --git a/web/core/components/pages/editor/toolbar/index.ts b/web/core/components/pages/editor/toolbar/index.ts
index d87f5d11946..66652b2dbd0 100644
--- a/web/core/components/pages/editor/toolbar/index.ts
+++ b/web/core/components/pages/editor/toolbar/index.ts
@@ -1,7 +1,5 @@
export * from "./color-dropdown";
-export * from "./extra-options";
export * from "./info-popover";
export * from "./options-dropdown";
export * from "./root";
-export * from "./mobile-root";
export * from "./toolbar";
diff --git a/web/core/components/pages/editor/toolbar/info-popover.tsx b/web/core/components/pages/editor/toolbar/info-popover.tsx
index ea9b734da10..d5a28145949 100644
--- a/web/core/components/pages/editor/toolbar/info-popover.tsx
+++ b/web/core/components/pages/editor/toolbar/info-popover.tsx
@@ -1,28 +1,25 @@
import { useState } from "react";
+import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { usePopper } from "react-popper";
import { Info } from "lucide-react";
-// plane editor
-import { EditorRefApi } from "@plane/editor";
-// plane ui
+// plane imports
import { Avatar } from "@plane/ui";
-// plane utils
import { getFileURL, renderFormattedDate } from "@plane/utils";
// helpers
-import { getReadTimeFromWordsCount } from "@/helpers/date-time.helper";
+import { calculateTimeAgoShort, getReadTimeFromWordsCount } from "@/helpers/date-time.helper";
// hooks
import { useMember } from "@/hooks/store";
// store types
import { TPageInstance } from "@/store/pages/base-page";
type Props = {
- editorRef: EditorRefApi | null;
page: TPageInstance;
};
-export const PageInfoPopover: React.FC = (props) => {
- const { editorRef, page } = props;
+export const PageInfoPopover: React.FC = observer((props) => {
+ const { page } = props;
// states
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
// refs
@@ -40,7 +37,7 @@ export const PageInfoPopover: React.FC = (props) => {
const editorInformation = page.updated_by ? getUserDetails(page.updated_by) : undefined;
const creatorInformation = page.created_by ? getUserDetails(page.created_by) : undefined;
- const documentsInfo = editorRef?.getDocumentInfo() || { words: 0, characters: 0, paragraphs: 0 };
+ const documentsInfo = page.editorRef?.getDocumentInfo() || { words: 0, characters: 0, paragraphs: 0 };
const secondsToReadableTime = () => {
const wordsCount = documentsInfo.words;
@@ -72,8 +69,16 @@ export const PageInfoPopover: React.FC = (props) => {
];
return (
- setIsPopoverOpen(true)} onMouseLeave={() => setIsPopoverOpen(false)}>
-
);
-};
+});
diff --git a/web/core/components/pages/editor/toolbar/mobile-root.tsx b/web/core/components/pages/editor/toolbar/mobile-root.tsx
deleted file mode 100644
index ccc04ab6ebb..00000000000
--- a/web/core/components/pages/editor/toolbar/mobile-root.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { observer } from "mobx-react";
-// plane imports
-import { EditorRefApi } from "@plane/editor";
-import { Header, EHeaderVariant } from "@plane/ui";
-// components
-import { PageExtraOptions, PageToolbar } from "@/components/pages";
-// hooks
-import { usePageFilters } from "@/hooks/use-page-filters";
-// plane web hooks
-import { EPageStoreType } from "@/plane-web/hooks/store";
-// store
-import { TPageInstance } from "@/store/pages/base-page";
-
-type Props = {
- editorRef: EditorRefApi;
- page: TPageInstance;
- storeType: EPageStoreType;
-};
-
-export const PageEditorMobileHeaderRoot: React.FC = observer((props) => {
- const { editorRef, page, storeType } = props;
- // derived values
- const { isContentEditable } = page;
- // page filters
- const { isStickyToolbarEnabled } = usePageFilters();
-
- return (
- <>
-
-
- {isContentEditable && editorRef && }
-
- >
- );
-});
diff --git a/web/core/components/pages/editor/toolbar/options-dropdown.tsx b/web/core/components/pages/editor/toolbar/options-dropdown.tsx
index 38f0a4e239e..4dbdbf50acc 100644
--- a/web/core/components/pages/editor/toolbar/options-dropdown.tsx
+++ b/web/core/components/pages/editor/toolbar/options-dropdown.tsx
@@ -4,9 +4,7 @@ import { useMemo, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
import { ArrowUpToLine, Clipboard, History } from "lucide-react";
-// document editor
-import { EditorRefApi } from "@plane/editor";
-// ui
+// plane imports
import { TContextMenuItem, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
// components
import { ExportPageModal, PageActions, TPageActions } from "@/components/pages";
@@ -21,19 +19,18 @@ import { EPageStoreType } from "@/plane-web/hooks/store";
import { TPageInstance } from "@/store/pages/base-page";
type Props = {
- editorRef: EditorRefApi | null;
page: TPageInstance;
storeType: EPageStoreType;
};
export const PageOptionsDropdown: React.FC = observer((props) => {
- const { editorRef, page, storeType } = props;
+ const { page, storeType } = props;
// states
const [isExportModalOpen, setIsExportModalOpen] = useState(false);
// router
const router = useRouter();
// store values
- const { name, isContentEditable } = page;
+ const { name, isContentEditable, editorRef } = page;
// page filters
const { isFullWidth, handleFullWidth, isStickyToolbarEnabled, handleStickyToolbar } = usePageFilters();
// update query params
@@ -127,10 +124,7 @@ export const PageOptionsDropdown: React.FC = observer((props) => {
optionsOrder={[
"full-screen",
"sticky-toolbar",
- "copy-link",
"make-a-copy",
- "move",
- "toggle-lock",
"toggle-access",
"archive-restore",
"delete",
diff --git a/web/core/components/pages/editor/toolbar/root.tsx b/web/core/components/pages/editor/toolbar/root.tsx
index 901ce7f5ea5..8ce0bd005d9 100644
--- a/web/core/components/pages/editor/toolbar/root.tsx
+++ b/web/core/components/pages/editor/toolbar/root.tsx
@@ -1,58 +1,48 @@
import { observer } from "mobx-react";
-import { EditorRefApi } from "@plane/editor";
// components
-import { PageEditorMobileHeaderRoot, PageExtraOptions, PageToolbar } from "@/components/pages";
+import { PageToolbar } from "@/components/pages";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { usePageFilters } from "@/hooks/use-page-filters";
-// plane web hooks
-import { EPageStoreType } from "@/plane-web/hooks/store";
+// plane web components
+import { PageCollaboratorsList } from "@/plane-web/components/pages/header/collaborators-list";
// store
import { TPageInstance } from "@/store/pages/base-page";
type Props = {
- editorReady: boolean;
- editorRef: React.RefObject;
page: TPageInstance;
- storeType: EPageStoreType;
};
export const PageEditorToolbarRoot: React.FC = observer((props) => {
- const { editorReady, editorRef, page, storeType } = props;
+ const { page } = props;
// derived values
- const { isContentEditable } = page;
+ const { isContentEditable, editorRef } = page;
// page filters
const { isFullWidth, isStickyToolbarEnabled } = usePageFilters();
// derived values
- const resolvedEditorRef = editorRef.current;
const shouldHideToolbar = !isStickyToolbarEnabled || !isContentEditable;
- if (!resolvedEditorRef) return null;
-
return (
-
+
-
- {editorReady && resolvedEditorRef && (
-
- )}
-
-
+ {editorRef &&
}
+
-
);
});
diff --git a/web/core/components/pages/editor/toolbar/toolbar.tsx b/web/core/components/pages/editor/toolbar/toolbar.tsx
index a9c374f685a..f6dd3069166 100644
--- a/web/core/components/pages/editor/toolbar/toolbar.tsx
+++ b/web/core/components/pages/editor/toolbar/toolbar.tsx
@@ -15,7 +15,6 @@ import { cn } from "@/helpers/common.helper";
type Props = {
editorRef: EditorRefApi;
- isHidden: boolean;
};
type ToolbarButtonProps = {
@@ -65,7 +64,7 @@ ToolbarButton.displayName = "ToolbarButton";
const toolbarItems = TOOLBAR_ITEMS.document;
export const PageToolbar: React.FC
= (props) => {
- const { editorRef, isHidden } = props;
+ const { editorRef } = props;
// states
const [activeStates, setActiveStates] = useState>({});
@@ -98,14 +97,7 @@ export const PageToolbar: React.FC = (props) => {
);
return (
-
+
@@ -139,20 +131,22 @@ export const PageToolbar: React.FC = (props) => {
))}
-
- editorRef.executeMenuItemCommand({
- itemKey: key,
- color,
- })
- }
- isColorActive={(key, color) =>
- editorRef.isMenuItemActive({
- itemKey: key,
- color,
- })
- }
- />
+
+
+ editorRef.executeMenuItemCommand({
+ itemKey: key,
+ color,
+ })
+ }
+ isColorActive={(key, color) =>
+ editorRef.isMenuItemActive({
+ itemKey: key,
+ color,
+ })
+ }
+ />
+
{Object.keys(toolbarItems).map((key) => (
{toolbarItems[key].map((item) => (
diff --git a/web/core/components/pages/header/actions.tsx b/web/core/components/pages/header/actions.tsx
new file mode 100644
index 00000000000..c9e3843704b
--- /dev/null
+++ b/web/core/components/pages/header/actions.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { observer } from "mobx-react";
+// components
+import { PageInfoPopover, PageOptionsDropdown } from "@/components/pages";
+// plane web components
+import { PageLockControl } from "@/plane-web/components/pages/header/lock-control";
+import { PageMoveControl } from "@/plane-web/components/pages/header/move-control";
+// plane web hooks
+import { EPageStoreType } from "@/plane-web/hooks/store";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+// local imports
+import { PageArchivedBadge } from "./archived-badge";
+import { PageCopyLinkControl } from "./copy-link-control";
+import { PageOfflineBadge } from "./offline-badge";
+
+type Props = {
+ page: TPageInstance;
+ storeType: EPageStoreType;
+};
+
+export const PageHeaderActions: React.FC
= observer((props) => {
+ const { page, storeType } = props;
+
+ return (
+
+ );
+});
diff --git a/web/core/components/pages/header/archived-badge.tsx b/web/core/components/pages/header/archived-badge.tsx
new file mode 100644
index 00000000000..24f239c875b
--- /dev/null
+++ b/web/core/components/pages/header/archived-badge.tsx
@@ -0,0 +1,21 @@
+import { observer } from "mobx-react";
+// plane imports
+import { ArchiveIcon } from "@plane/ui";
+import { renderFormattedDate } from "@plane/utils";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+
+type Props = {
+ page: TPageInstance;
+};
+
+export const PageArchivedBadge = observer(({ page }: Props) => {
+ if (!page.archived_at) return null;
+
+ return (
+
+
+
Archived at {renderFormattedDate(page.archived_at)}
+
+ );
+});
diff --git a/web/core/components/pages/header/copy-link-control.tsx b/web/core/components/pages/header/copy-link-control.tsx
new file mode 100644
index 00000000000..a498e3ab93c
--- /dev/null
+++ b/web/core/components/pages/header/copy-link-control.tsx
@@ -0,0 +1,27 @@
+import { observer } from "mobx-react";
+import { Link } from "lucide-react";
+// hooks
+import { usePageOperations } from "@/hooks/use-page-operations";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+
+type Props = {
+ page: TPageInstance;
+};
+
+export const PageCopyLinkControl = observer(({ page }: Props) => {
+ // page operations
+ const { pageOperations } = usePageOperations({
+ page,
+ });
+
+ return (
+
+
+
+ );
+});
diff --git a/web/core/components/pages/header/favorite-control.tsx b/web/core/components/pages/header/favorite-control.tsx
new file mode 100644
index 00000000000..948ce4f522b
--- /dev/null
+++ b/web/core/components/pages/header/favorite-control.tsx
@@ -0,0 +1,31 @@
+import { observer } from "mobx-react";
+// plane imports
+import { FavoriteStar } from "@plane/ui";
+// hooks
+import { usePageOperations } from "@/hooks/use-page-operations";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+
+type Props = {
+ page: TPageInstance;
+};
+
+export const PageFavoriteControl = observer(({ page }: Props) => {
+ // derived values
+ const { is_favorite, canCurrentUserFavoritePage } = page;
+ // page operations
+ const { pageOperations } = usePageOperations({
+ page,
+ });
+
+ if (!canCurrentUserFavoritePage) return null;
+
+ return (
+
+ );
+});
diff --git a/web/core/components/pages/header/offline-badge.tsx b/web/core/components/pages/header/offline-badge.tsx
new file mode 100644
index 00000000000..31e68e576c2
--- /dev/null
+++ b/web/core/components/pages/header/offline-badge.tsx
@@ -0,0 +1,30 @@
+import { observer } from "mobx-react";
+// plane imports
+import { Tooltip } from "@plane/ui";
+// hooks
+import useOnlineStatus from "@/hooks/use-online-status";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+
+type Props = {
+ page: TPageInstance;
+};
+
+export const PageOfflineBadge = observer(({ page }: Props) => {
+ // use online status
+ const { isOnline } = useOnlineStatus();
+
+ if (!page.isContentEditable || isOnline) return null;
+
+ return (
+
+
+
+ Offline
+
+
+ );
+});
diff --git a/web/core/hooks/use-page-operations.ts b/web/core/hooks/use-page-operations.ts
index c893126b8b8..ab4d2e7ab57 100644
--- a/web/core/hooks/use-page-operations.ts
+++ b/web/core/hooks/use-page-operations.ts
@@ -47,7 +47,7 @@ export const usePageOperations = (
// collaborative actions
const { executeCollaborativeAction } = useCollaborativePageActions(props);
// local storage
- const { setValue: toggleFavoriteMenu, storedValue: isfavoriteMenuOpen } = useLocalStorage(
+ const { setValue: toggleFavoriteMenu, storedValue: isFavoriteMenuOpen } = useLocalStorage(
IS_FAVORITE_MENU_OPEN,
false
);
@@ -147,7 +147,7 @@ export const usePageOperations = (
);
} else {
addToFavorites().then(() => {
- if (!isfavoriteMenuOpen) toggleFavoriteMenu(true);
+ if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
@@ -199,7 +199,9 @@ export const usePageOperations = (
getRedirectionLink,
is_favorite,
is_locked,
+ isFavoriteMenuOpen,
removePageFromFavorites,
+ toggleFavoriteMenu,
]);
return {
pageOperations,
diff --git a/web/core/store/pages/base-page.ts b/web/core/store/pages/base-page.ts
index 6bba1c0e05a..32a087c95ff 100644
--- a/web/core/store/pages/base-page.ts
+++ b/web/core/store/pages/base-page.ts
@@ -2,6 +2,7 @@ import set from "lodash/set";
import { action, computed, makeObservable, observable, reaction, runInAction } from "mobx";
// plane imports
import { EPageAccess } from "@plane/constants";
+import { EditorRefApi } from "@plane/editor";
import { TDocumentPayload, TLogoProps, TNameDescriptionLoader, TPage } from "@plane/types";
import { TChangeHandlerProps } from "@plane/ui";
import { convertHexEmojiToDecimal } from "@plane/utils";
@@ -11,6 +12,7 @@ import { RootStore } from "@/plane-web/store/root.store";
export type TBasePage = TPage & {
// observables
isSubmitting: TNameDescriptionLoader;
+ editorRef: EditorRefApi | null;
// computed
asJSON: TPage | undefined;
isCurrentUserOwner: boolean;
@@ -32,6 +34,8 @@ export type TBasePage = TPage & {
addToFavorites: () => Promise;
removePageFromFavorites: () => Promise;
duplicate: () => Promise;
+ mutateProperties: (data: Partial, shouldUpdateName?: boolean) => void;
+ setEditorRef: (editorRef: EditorRefApi | null) => void;
};
export type TBasePagePermissions = {
@@ -68,6 +72,7 @@ export type TPageInstance = TBasePage &
export class BasePage implements TBasePage {
// loaders
isSubmitting: TNameDescriptionLoader = "saved";
+ editorRef: EditorRefApi | null = null;
// page properties
id: string | undefined;
name: string | undefined;
@@ -125,6 +130,7 @@ export class BasePage implements TBasePage {
makeObservable(this, {
// loaders
isSubmitting: observable.ref,
+ editorRef: observable.ref,
// page properties
id: observable.ref,
name: observable.ref,
@@ -165,6 +171,8 @@ export class BasePage implements TBasePage {
addToFavorites: action,
removePageFromFavorites: action,
duplicate: action,
+ mutateProperties: action,
+ setEditorRef: action,
});
this.rootStore = store;
@@ -426,25 +434,34 @@ export class BasePage implements TBasePage {
};
updatePageLogo = async (value: TChangeHandlerProps) => {
- let logoValue = {};
- if (value?.type === "emoji")
- logoValue = {
- value: convertHexEmojiToDecimal(value.value.unified),
- url: value.value.imageUrl,
+ const originalLogoProps = { ...this.logo_props };
+ try {
+ let logoValue = {};
+ if (value?.type === "emoji")
+ logoValue = {
+ value: convertHexEmojiToDecimal(value.value.unified),
+ url: value.value.imageUrl,
+ };
+ else if (value?.type === "icon") logoValue = value.value;
+
+ const logoProps: TLogoProps = {
+ in_use: value?.type,
+ [value?.type]: logoValue,
};
- else if (value?.type === "icon") logoValue = value.value;
-
- const logoProps: TLogoProps = {
- in_use: value?.type,
- [value?.type]: logoValue,
- };
- await this.services.update({
- logo_props: logoProps,
- });
- runInAction(() => {
- this.logo_props = logoProps;
- });
+ runInAction(() => {
+ this.logo_props = logoProps;
+ });
+ await this.services.update({
+ logo_props: logoProps,
+ });
+ } catch (error) {
+ console.error("Error in updating page logo", error);
+ runInAction(() => {
+ this.logo_props = originalLogoProps as TLogoProps;
+ });
+ throw error;
+ }
};
/**
@@ -498,4 +515,23 @@ export class BasePage implements TBasePage {
* @description duplicate the page
*/
duplicate = async () => await this.services.duplicate();
+
+ /**
+ * @description mutate multiple properties at once
+ * @param data Partial
+ */
+ mutateProperties = (data: Partial, shouldUpdateName: boolean = true) => {
+ Object.keys(data).forEach((key) => {
+ const value = data[key as keyof TPage];
+ if (key === "name" && !shouldUpdateName) return;
+ set(this, key, value);
+ });
+ };
+
+ setEditorRef = (editorRef: EditorRefApi | null) => {
+ console.log("store editorRef", editorRef);
+ runInAction(() => {
+ this.editorRef = editorRef;
+ });
+ };
}
diff --git a/web/core/store/pages/project-page.store.ts b/web/core/store/pages/project-page.store.ts
index e3ddacb5fff..b2f7b6a1ef3 100644
--- a/web/core/store/pages/project-page.store.ts
+++ b/web/core/store/pages/project-page.store.ts
@@ -206,7 +206,19 @@ export class ProjectPageStore implements IProjectPageStore {
const pages = await this.service.fetchAll(workspaceSlug, projectId);
runInAction(() => {
- for (const page of pages) if (page?.id) set(this.data, [page.id], new ProjectPage(this.store, page));
+ for (const page of pages) {
+ if (page?.id) {
+ const existingPage = this.getPageById(page.id);
+ if (existingPage) {
+ // If page already exists, update all fields except name
+ const { name, ...otherFields } = page;
+ existingPage.mutateProperties(otherFields, false);
+ } else {
+ // If new page, create a new instance with all data
+ set(this.data, [page.id], new ProjectPage(this.store, page));
+ }
+ }
+ }
this.loader = undefined;
});
@@ -238,8 +250,16 @@ export class ProjectPageStore implements IProjectPageStore {
});
const page = await this.service.fetchById(workspaceSlug, projectId, pageId);
+ const pageInstance = page?.id ? this.getPageById(page.id) : undefined;
+
runInAction(() => {
- if (page?.id) set(this.data, [page.id], new ProjectPage(this.store, page));
+ if (page?.id) {
+ if (pageInstance) {
+ pageInstance.mutateProperties(page, false);
+ } else {
+ set(this.data, [page.id], new ProjectPage(this.store, page));
+ }
+ }
this.loader = undefined;
});
diff --git a/web/styles/globals.css b/web/styles/globals.css
index 68e81e0984a..55c8c869ee7 100644
--- a/web/styles/globals.css
+++ b/web/styles/globals.css
@@ -854,3 +854,91 @@ div.web-view-spinner div.bar12 {
.epr-search-container > .epr-icn-search {
color: rgb(var(--color-text-400)) !important;
}
+
+/* Lock icon animations */
+@keyframes textSlideIn {
+ 0% {
+ opacity: 0;
+ transform: translateX(-8px);
+ max-width: 0px;
+ }
+ 40% {
+ opacity: 0.7;
+ max-width: 60px;
+ }
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ max-width: 60px;
+ }
+}
+
+@keyframes textFadeOut {
+ 0% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ 100% {
+ opacity: 0;
+ transform: translateX(8px);
+ }
+}
+
+@keyframes lockIconAnimation {
+ 0% {
+ transform: rotate(-5deg) scale(1);
+ }
+ 25% {
+ transform: rotate(0deg) scale(1.15);
+ }
+ 50% {
+ transform: rotate(5deg) scale(1.08);
+ }
+ 100% {
+ transform: rotate(0deg) scale(1);
+ }
+}
+
+@keyframes unlockIconAnimation {
+ 0% {
+ transform: rotate(0deg) scale(1);
+ }
+ 40% {
+ transform: rotate(-8deg) scale(1.15);
+ }
+ 80% {
+ transform: rotate(3deg) scale(1.05);
+ }
+ 100% {
+ transform: rotate(0deg) scale(1);
+ }
+}
+
+@keyframes fadeOut {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.animate-text-slide-in {
+ animation: textSlideIn 400ms ease-out forwards;
+}
+
+.animate-text-fade-out {
+ animation: textFadeOut 600ms ease-in 300ms forwards;
+}
+
+.animate-lock-icon {
+ animation: lockIconAnimation 600ms ease-out forwards;
+}
+
+.animate-unlock-icon {
+ animation: unlockIconAnimation 600ms ease-out forwards;
+}
+
+.animate-fade-out {
+ animation: fadeOut 500ms ease-in 100ms forwards;
+}