[PE-155] chore: floating toolbar for pages (#6482)
* chore: add floating toolbar to pages * fix: locked page toolbar
This commit is contained in:
parent
421839ec51
commit
b698f44500
8 changed files with 67 additions and 10 deletions
|
|
@ -16,6 +16,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
|
||||||
const {
|
const {
|
||||||
onTransaction,
|
onTransaction,
|
||||||
aiHandler,
|
aiHandler,
|
||||||
|
bubbleMenuEnabled = true,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
disabledExtensions,
|
disabledExtensions,
|
||||||
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
||||||
|
|
@ -75,8 +76,9 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageRenderer
|
<PageRenderer
|
||||||
displayConfig={displayConfig}
|
|
||||||
aiHandler={aiHandler}
|
aiHandler={aiHandler}
|
||||||
|
bubbleMenuEnabled={bubbleMenuEnabled}
|
||||||
|
displayConfig={displayConfig}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
editorContainerClassName={editorContainerClassNames}
|
editorContainerClassName={editorContainerClassNames}
|
||||||
id={id}
|
id={id}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@ import { Editor, ReactRenderer } from "@tiptap/react";
|
||||||
// components
|
// components
|
||||||
import { EditorContainer, EditorContentWrapper } from "@/components/editors";
|
import { EditorContainer, EditorContentWrapper } from "@/components/editors";
|
||||||
import { LinkView, LinkViewProps } from "@/components/links";
|
import { LinkView, LinkViewProps } from "@/components/links";
|
||||||
import { AIFeaturesMenu, BlockMenu } from "@/components/menus";
|
import { AIFeaturesMenu, BlockMenu, EditorBubbleMenu } from "@/components/menus";
|
||||||
// types
|
// types
|
||||||
import { TAIHandler, TDisplayConfig } from "@/types";
|
import { TAIHandler, TDisplayConfig } from "@/types";
|
||||||
|
|
||||||
type IPageRenderer = {
|
type IPageRenderer = {
|
||||||
aiHandler?: TAIHandler;
|
aiHandler?: TAIHandler;
|
||||||
|
bubbleMenuEnabled: boolean;
|
||||||
displayConfig: TDisplayConfig;
|
displayConfig: TDisplayConfig;
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
editorContainerClassName: string;
|
editorContainerClassName: string;
|
||||||
|
|
@ -29,7 +30,7 @@ type IPageRenderer = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageRenderer = (props: IPageRenderer) => {
|
export const PageRenderer = (props: IPageRenderer) => {
|
||||||
const { aiHandler, displayConfig, editor, editorContainerClassName, id, tabIndex } = props;
|
const { aiHandler, bubbleMenuEnabled, displayConfig, editor, editorContainerClassName, id, tabIndex } = props;
|
||||||
// states
|
// states
|
||||||
const [linkViewProps, setLinkViewProps] = useState<LinkViewProps>();
|
const [linkViewProps, setLinkViewProps] = useState<LinkViewProps>();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
@ -141,6 +142,7 @@ export const PageRenderer = (props: IPageRenderer) => {
|
||||||
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
|
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
|
||||||
{editor.isEditable && (
|
{editor.isEditable && (
|
||||||
<div>
|
<div>
|
||||||
|
{bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}
|
||||||
<BlockMenu editor={editor} />
|
<BlockMenu editor={editor} />
|
||||||
<AIFeaturesMenu menu={aiHandler?.menu} />
|
<AIFeaturesMenu menu={aiHandler?.menu} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageRenderer
|
<PageRenderer
|
||||||
|
bubbleMenuEnabled={false}
|
||||||
displayConfig={displayConfig}
|
displayConfig={displayConfig}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
editorContainerClassName={editorContainerClassName}
|
editorContainerClassName={editorContainerClassName}
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,9 @@ export interface IRichTextEditor extends IEditorProps {
|
||||||
|
|
||||||
export interface ICollaborativeDocumentEditor
|
export interface ICollaborativeDocumentEditor
|
||||||
extends Omit<IEditorProps, "initialValue" | "onChange" | "onEnterKeyPress" | "value"> {
|
extends Omit<IEditorProps, "initialValue" | "onChange" | "onEnterKeyPress" | "value"> {
|
||||||
editable: boolean;
|
|
||||||
aiHandler?: TAIHandler;
|
aiHandler?: TAIHandler;
|
||||||
|
bubbleMenuEnabled?: boolean;
|
||||||
|
editable: boolean;
|
||||||
embedHandler: TEmbedConfig;
|
embedHandler: TEmbedConfig;
|
||||||
handleEditorReady?: (value: boolean) => void;
|
handleEditorReady?: (value: boolean) => void;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import { TPageInstance } from "@/store/pages/base-page";
|
||||||
|
|
||||||
export type TPageActions =
|
export type TPageActions =
|
||||||
| "full-screen"
|
| "full-screen"
|
||||||
|
| "sticky-toolbar"
|
||||||
| "copy-markdown"
|
| "copy-markdown"
|
||||||
| "toggle-lock"
|
| "toggle-lock"
|
||||||
| "toggle-access"
|
| "toggle-access"
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store values
|
// store values
|
||||||
const { name } = page;
|
const { name, isContentEditable } = page;
|
||||||
// page filters
|
// page filters
|
||||||
const { isFullWidth, handleFullWidth } = usePageFilters();
|
const { isFullWidth, handleFullWidth, isStickyToolbarEnabled, handleStickyToolbar } = usePageFilters();
|
||||||
// update query params
|
// update query params
|
||||||
const { updateQueryParams } = useQueryParams();
|
const { updateQueryParams } = useQueryParams();
|
||||||
// menu items list
|
// menu items list
|
||||||
|
|
@ -49,6 +49,18 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
||||||
),
|
),
|
||||||
className: "flex items-center justify-between gap-2",
|
className: "flex items-center justify-between gap-2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "sticky-toolbar",
|
||||||
|
action: () => handleStickyToolbar(!isStickyToolbarEnabled),
|
||||||
|
customContent: (
|
||||||
|
<>
|
||||||
|
Sticky toolbar
|
||||||
|
<ToggleSwitch value={isStickyToolbarEnabled} onChange={() => {}} />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
className: "flex items-center justify-between gap-2",
|
||||||
|
shouldRender: isContentEditable,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "copy-markdown",
|
key: "copy-markdown",
|
||||||
action: () => {
|
action: () => {
|
||||||
|
|
@ -86,7 +98,16 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
||||||
shouldRender: true,
|
shouldRender: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[editorRef, handleFullWidth, isFullWidth, router, updateQueryParams]
|
[
|
||||||
|
editorRef,
|
||||||
|
handleFullWidth,
|
||||||
|
handleStickyToolbar,
|
||||||
|
isContentEditable,
|
||||||
|
isFullWidth,
|
||||||
|
isStickyToolbarEnabled,
|
||||||
|
router,
|
||||||
|
updateQueryParams,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -102,6 +123,7 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
||||||
extraOptions={EXTRA_MENU_OPTIONS}
|
extraOptions={EXTRA_MENU_OPTIONS}
|
||||||
optionsOrder={[
|
optionsOrder={[
|
||||||
"full-screen",
|
"full-screen",
|
||||||
|
"sticky-toolbar",
|
||||||
"copy-link",
|
"copy-link",
|
||||||
"make-a-copy",
|
"make-a-copy",
|
||||||
"move",
|
"move",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
||||||
// derived values
|
// derived values
|
||||||
const { isContentEditable } = page;
|
const { isContentEditable } = page;
|
||||||
// page filters
|
// page filters
|
||||||
const { isFullWidth } = usePageFilters();
|
const { isFullWidth, isStickyToolbarEnabled } = usePageFilters();
|
||||||
// derived values
|
// derived values
|
||||||
const resolvedEditorRef = editorRef.current;
|
const resolvedEditorRef = editorRef.current;
|
||||||
|
|
||||||
|
|
@ -48,7 +48,9 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{editorReady && isContentEditable && editorRef.current && <PageToolbar editorRef={editorRef?.current} />}
|
{isStickyToolbarEnabled && editorReady && isContentEditable && editorRef.current && (
|
||||||
|
<PageToolbar editorRef={editorRef?.current} />
|
||||||
|
)}
|
||||||
</Header.LeftItem>
|
</Header.LeftItem>
|
||||||
<PageExtraOptions editorRef={resolvedEditorRef} page={page} />
|
<PageExtraOptions editorRef={resolvedEditorRef} page={page} />
|
||||||
</Header>
|
</Header>
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ export type TPagesPersonalizationConfig = {
|
||||||
full_width: boolean;
|
full_width: boolean;
|
||||||
font_size: TEditorFontSize;
|
font_size: TEditorFontSize;
|
||||||
font_style: TEditorFontStyle;
|
font_style: TEditorFontStyle;
|
||||||
|
sticky_toolbar: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_PERSONALIZATION_VALUES: TPagesPersonalizationConfig = {
|
const DEFAULT_PERSONALIZATION_VALUES: TPagesPersonalizationConfig = {
|
||||||
full_width: false,
|
full_width: false,
|
||||||
font_size: "large-font",
|
font_size: "large-font",
|
||||||
font_style: "sans-serif",
|
font_style: "sans-serif",
|
||||||
|
sticky_toolbar: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePageFilters = () => {
|
export const usePageFilters = () => {
|
||||||
|
|
@ -23,7 +25,17 @@ export const usePageFilters = () => {
|
||||||
DEFAULT_PERSONALIZATION_VALUES
|
DEFAULT_PERSONALIZATION_VALUES
|
||||||
);
|
);
|
||||||
// stored values
|
// stored values
|
||||||
const isFullWidth = useMemo(() => !!pagesConfig?.full_width, [pagesConfig?.full_width]);
|
const isFullWidth = useMemo(
|
||||||
|
() => (pagesConfig?.full_width === undefined ? DEFAULT_PERSONALIZATION_VALUES.full_width : pagesConfig?.full_width),
|
||||||
|
[pagesConfig?.full_width]
|
||||||
|
);
|
||||||
|
const isStickyToolbarEnabled = useMemo(
|
||||||
|
() =>
|
||||||
|
pagesConfig?.sticky_toolbar === undefined
|
||||||
|
? DEFAULT_PERSONALIZATION_VALUES.sticky_toolbar
|
||||||
|
: pagesConfig?.sticky_toolbar,
|
||||||
|
[pagesConfig?.sticky_toolbar]
|
||||||
|
);
|
||||||
const fontSize = useMemo(
|
const fontSize = useMemo(
|
||||||
() => pagesConfig?.font_size ?? DEFAULT_PERSONALIZATION_VALUES.font_size,
|
() => pagesConfig?.font_size ?? DEFAULT_PERSONALIZATION_VALUES.font_size,
|
||||||
[pagesConfig?.font_size]
|
[pagesConfig?.font_size]
|
||||||
|
|
@ -78,6 +90,18 @@ export const usePageFilters = () => {
|
||||||
},
|
},
|
||||||
[handleUpdateConfig]
|
[handleUpdateConfig]
|
||||||
);
|
);
|
||||||
|
/**
|
||||||
|
* @description action to update full_width value
|
||||||
|
* @param {boolean} value
|
||||||
|
*/
|
||||||
|
const handleStickyToolbar = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
handleUpdateConfig({
|
||||||
|
sticky_toolbar: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[handleUpdateConfig]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fontSize,
|
fontSize,
|
||||||
|
|
@ -86,5 +110,7 @@ export const usePageFilters = () => {
|
||||||
handleFontStyle,
|
handleFontStyle,
|
||||||
isFullWidth,
|
isFullWidth,
|
||||||
handleFullWidth,
|
handleFullWidth,
|
||||||
|
isStickyToolbarEnabled,
|
||||||
|
handleStickyToolbar,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue