♻️(frontend) add versions in the panel editor
We add the features version to the panel editor. We had to refactor the panel to be able to have the version with the table of content in the same panel.
This commit is contained in:
@@ -1,106 +0,0 @@
|
||||
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Card, IconBG, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
interface PanelProps {
|
||||
title?: string;
|
||||
setIsPanelOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const Panel = ({
|
||||
children,
|
||||
title,
|
||||
setIsPanelOpen,
|
||||
}: PropsWithChildren<PanelProps>) => {
|
||||
const { t } = useTranslation();
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
|
||||
const closedOverridingStyles = !isOpen && {
|
||||
$width: '0',
|
||||
$maxWidth: '0',
|
||||
$minWidth: '0',
|
||||
};
|
||||
|
||||
const transition = 'all 0.5s ease-in-out';
|
||||
|
||||
return (
|
||||
<Card
|
||||
$width="100%"
|
||||
$maxWidth="20rem"
|
||||
$position="sticky"
|
||||
$maxHeight="99vh"
|
||||
$height="100%"
|
||||
$css={`
|
||||
top: 0vh;
|
||||
transition: ${transition};
|
||||
${
|
||||
!isOpen &&
|
||||
`
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
`
|
||||
}
|
||||
`}
|
||||
aria-label={t('Document panel')}
|
||||
{...closedOverridingStyles}
|
||||
>
|
||||
<Box
|
||||
$overflow="inherit"
|
||||
$position="sticky"
|
||||
$css={`
|
||||
top: 0;
|
||||
opacity: ${isOpen ? '1' : '0'};
|
||||
transition: ${transition};
|
||||
`}
|
||||
$maxHeight="100%"
|
||||
>
|
||||
<Box
|
||||
$padding={{ all: 'small' }}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify="center"
|
||||
$css={`border-top: 2px solid ${colorsTokens()['primary-600']};`}
|
||||
>
|
||||
<IconBG
|
||||
iconName="menu_open"
|
||||
aria-label={isOpen ? t('Close the panel') : t('Open the panel')}
|
||||
$background="transparent"
|
||||
$size="h2"
|
||||
$zIndex={1}
|
||||
$css={`
|
||||
cursor: pointer;
|
||||
left: 0rem;
|
||||
top: 0.1rem;
|
||||
transition: ${transition};
|
||||
transform: rotate(180deg);
|
||||
opacity: ${isOpen ? '1' : '0'};
|
||||
user-select: none;
|
||||
`}
|
||||
$position="absolute"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setTimeout(() => {
|
||||
setIsPanelOpen(false);
|
||||
}, 400);
|
||||
}}
|
||||
$radius="2px"
|
||||
/>
|
||||
{title && (
|
||||
<Text $weight="bold" $size="l" $theme="primary">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -5,19 +5,14 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Card, Text, TextErrors } from '@/components';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { DocHeader } from '@/features/docs/doc-header';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { TableContent } from '@/features/docs/doc-table-content';
|
||||
import {
|
||||
VersionList,
|
||||
Versions,
|
||||
useDocVersion,
|
||||
useDocVersionStore,
|
||||
} from '@/features/docs/doc-versioning/';
|
||||
import { useHeading } from '@/features/docs/doc-table-content';
|
||||
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
||||
|
||||
import { BlockNoteEditor } from './BlockNoteEditor';
|
||||
import { IconOpenPanelEditor, PanelEditor } from './PanelEditor';
|
||||
|
||||
interface DocEditorProps {
|
||||
doc: Doc;
|
||||
@@ -27,8 +22,8 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
||||
const {
|
||||
query: { versionId },
|
||||
} = useRouter();
|
||||
const { isPanelVersionOpen, setIsPanelVersionOpen } = useDocVersionStore();
|
||||
const { t } = useTranslation();
|
||||
const headings = useHeading(doc.id);
|
||||
|
||||
const isVersion = versionId && typeof versionId === 'string';
|
||||
|
||||
@@ -56,21 +51,22 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
||||
$height="100%"
|
||||
$direction="row"
|
||||
$margin={{ all: 'small', top: 'none' }}
|
||||
$gap="1rem"
|
||||
$css="overflow-x: clip;"
|
||||
>
|
||||
<Card $padding="big" $css="flex:1;" $overflow="auto">
|
||||
<Card
|
||||
$padding="big"
|
||||
$css="flex:1;"
|
||||
$overflow="auto"
|
||||
$position="relative"
|
||||
>
|
||||
{isVersion ? (
|
||||
<DocVersionEditor doc={doc} versionId={versionId} />
|
||||
) : (
|
||||
<BlockNoteEditor doc={doc} />
|
||||
)}
|
||||
<IconOpenPanelEditor headings={headings} />
|
||||
</Card>
|
||||
{doc.abilities.versions_list && isPanelVersionOpen && (
|
||||
<Panel title={t('VERSIONS')} setIsPanelOpen={setIsPanelVersionOpen}>
|
||||
<VersionList doc={doc} />
|
||||
</Panel>
|
||||
)}
|
||||
<TableContent doc={doc} />
|
||||
<PanelEditor doc={doc} headings={headings} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, BoxButton, Card, IconBG, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc } from '@/features/docs//doc-management';
|
||||
import { HeadingBlock, TableContent } from '@/features/docs/doc-table-content';
|
||||
import { VersionList } from '@/features/docs/doc-versioning';
|
||||
|
||||
import { usePanelEditorStore } from '../stores/usePanelEditorStore';
|
||||
|
||||
interface PanelProps {
|
||||
doc: Doc;
|
||||
headings: HeadingBlock[];
|
||||
}
|
||||
|
||||
export const PanelEditor = ({
|
||||
doc,
|
||||
headings,
|
||||
}: PropsWithChildren<PanelProps>) => {
|
||||
const { t } = useTranslation();
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const { isPanelTableContentOpen, setIsPanelTableContentOpen, isPanelOpen } =
|
||||
usePanelEditorStore();
|
||||
|
||||
return (
|
||||
<Card
|
||||
$width="100%"
|
||||
$maxWidth="20rem"
|
||||
$position="sticky"
|
||||
$maxHeight="99vh"
|
||||
$height="100%"
|
||||
$hasTransition="slow"
|
||||
$css={`
|
||||
top: 0vh;
|
||||
transform: translateX(0%);
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
${
|
||||
!isPanelOpen &&
|
||||
`
|
||||
transform: translateX(200%);
|
||||
opacity: 0;
|
||||
flex: 0;
|
||||
margin-left: 0rem;
|
||||
max-width: 0rem;
|
||||
`
|
||||
}
|
||||
`}
|
||||
aria-label={t('Document panel')}
|
||||
aria-hidden={!isPanelOpen}
|
||||
>
|
||||
<Box
|
||||
$overflow="inherit"
|
||||
$position="sticky"
|
||||
$hasTransition="slow"
|
||||
$css={`
|
||||
top: 0;
|
||||
opacity: ${isPanelOpen ? '1' : '0'};
|
||||
`}
|
||||
$maxHeight="100%"
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$align="center"
|
||||
$position="relative"
|
||||
$background={colorsTokens()['primary-400']}
|
||||
$margin={{ bottom: 'tiny' }}
|
||||
>
|
||||
<Box
|
||||
$background="white"
|
||||
$position="absolute"
|
||||
$height="100%"
|
||||
$width={doc.abilities.versions_list ? '50%' : '100%'}
|
||||
$hasTransition="slow"
|
||||
$css={`
|
||||
border-top: 2px solid ${colorsTokens()['primary-600']};
|
||||
${isPanelTableContentOpen ? 'transform: translateX(0);' : 'transform: translateX(100%);'}
|
||||
`}
|
||||
/>
|
||||
<BoxButton
|
||||
$minWidth={doc.abilities.versions_list ? '50%' : '100%'}
|
||||
onClick={() => setIsPanelTableContentOpen(true)}
|
||||
$zIndex={1}
|
||||
>
|
||||
<Text
|
||||
$width="100%"
|
||||
$weight="bold"
|
||||
$size="m"
|
||||
$theme="primary"
|
||||
$padding={{ vertical: 'small', horizontal: 'small' }}
|
||||
>
|
||||
{t('Table of content')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
{doc.abilities.versions_list && (
|
||||
<BoxButton
|
||||
$minWidth="50%"
|
||||
onClick={() => setIsPanelTableContentOpen(false)}
|
||||
$zIndex={1}
|
||||
>
|
||||
<Text
|
||||
$width="100%"
|
||||
$weight="bold"
|
||||
$size="m"
|
||||
$theme="primary"
|
||||
$padding={{ vertical: 'small', horizontal: 'small' }}
|
||||
>
|
||||
{t('Versions')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
)}
|
||||
</Box>
|
||||
{isPanelTableContentOpen && (
|
||||
<TableContent doc={doc} headings={headings} />
|
||||
)}
|
||||
{!isPanelTableContentOpen && doc.abilities.versions_list && (
|
||||
<VersionList doc={doc} />
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface IconOpenPanelEditorProps {
|
||||
headings: HeadingBlock[];
|
||||
}
|
||||
|
||||
export const IconOpenPanelEditor = ({ headings }: IconOpenPanelEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setIsPanelOpen, isPanelOpen, setIsPanelTableContentOpen } =
|
||||
usePanelEditorStore();
|
||||
const [hasBeenOpen, setHasBeenOpen] = useState(isPanelOpen);
|
||||
|
||||
const setClosePanel = () => {
|
||||
setHasBeenOpen(true);
|
||||
setIsPanelOpen(!isPanelOpen);
|
||||
};
|
||||
|
||||
// Open the panel if there are more than 1 heading
|
||||
useEffect(() => {
|
||||
if (headings?.length && headings.length > 1 && !hasBeenOpen) {
|
||||
setIsPanelTableContentOpen(true);
|
||||
setIsPanelOpen(true);
|
||||
setHasBeenOpen(true);
|
||||
}
|
||||
}, [headings, setIsPanelTableContentOpen, setIsPanelOpen, hasBeenOpen]);
|
||||
|
||||
// If open from the doc header we set the state as well
|
||||
useEffect(() => {
|
||||
if (isPanelOpen && !hasBeenOpen) {
|
||||
setHasBeenOpen(true);
|
||||
}
|
||||
}, [hasBeenOpen, isPanelOpen]);
|
||||
|
||||
// Close the panel unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setIsPanelOpen(false);
|
||||
};
|
||||
}, [setIsPanelOpen]);
|
||||
|
||||
return (
|
||||
<IconBG
|
||||
iconName="menu_open"
|
||||
aria-label={isPanelOpen ? t('Close the panel') : t('Open the panel')}
|
||||
$background="transparent"
|
||||
$size="h2"
|
||||
$zIndex={1}
|
||||
$hasTransition="slow"
|
||||
$css={`
|
||||
cursor: pointer;
|
||||
right: 0rem;
|
||||
top: 0.1rem;
|
||||
transform: rotate(${isPanelOpen ? '180deg' : '0deg'});
|
||||
user-select: none;
|
||||
${hasBeenOpen ? 'display:flex;' : 'display: none;'}
|
||||
`}
|
||||
$position="absolute"
|
||||
onClick={setClosePanel}
|
||||
$radius="2px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from './useDocStore';
|
||||
export * from './usePanelEditorStore';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface UsePanelEditorStore {
|
||||
isPanelOpen: boolean;
|
||||
setIsPanelOpen: (isOpen: boolean) => void;
|
||||
isPanelTableContentOpen: boolean;
|
||||
setIsPanelTableContentOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const usePanelEditorStore = create<UsePanelEditorStore>((set) => ({
|
||||
isPanelOpen: false,
|
||||
isPanelTableContentOpen: true,
|
||||
setIsPanelTableContentOpen: (isPanelTableContentOpen) => {
|
||||
set(() => ({ isPanelTableContentOpen }));
|
||||
},
|
||||
setIsPanelOpen: (isPanelOpen) => {
|
||||
set(() => ({ isPanelOpen }));
|
||||
},
|
||||
}));
|
||||
@@ -3,14 +3,13 @@ import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, DropButton, IconOptions, Text } from '@/components';
|
||||
import { usePanelEditorStore } from '@/features/docs/doc-editor/';
|
||||
import {
|
||||
Doc,
|
||||
ModalRemoveDoc,
|
||||
ModalShare,
|
||||
ModalUpdateDoc,
|
||||
} from '@/features/docs/doc-management';
|
||||
import { useDocTableContentStore } from '@/features/docs/doc-table-content';
|
||||
import { useDocVersionStore } from '@/features/docs/doc-versioning';
|
||||
|
||||
import { ModalPDF } from './ModalExport';
|
||||
|
||||
@@ -25,8 +24,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
||||
const [isDropOpen, setIsDropOpen] = useState(false);
|
||||
const { setIsPanelVersionOpen } = useDocVersionStore();
|
||||
const { setIsPanelTableContentOpen } = useDocTableContentStore();
|
||||
const { setIsPanelOpen, setIsPanelTableContentOpen } = usePanelEditorStore();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -81,10 +79,24 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
<Text $theme="primary">{t('Delete document')}</Text>
|
||||
</Button>
|
||||
)}
|
||||
{doc.abilities.versions_list && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsPanelOpen(true);
|
||||
setIsPanelTableContentOpen(false);
|
||||
setIsDropOpen(false);
|
||||
}}
|
||||
color="primary-text"
|
||||
icon={<span className="material-icons">history</span>}
|
||||
size="small"
|
||||
>
|
||||
<Text $theme="primary">{t('Version history')}</Text>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsPanelOpen(true);
|
||||
setIsPanelTableContentOpen(true);
|
||||
setIsPanelVersionOpen(false);
|
||||
setIsDropOpen(false);
|
||||
}}
|
||||
color="primary-text"
|
||||
|
||||
@@ -1,89 +1,25 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, BoxButton, Text } from '@/components';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { useDocStore } from '@/features/docs/doc-editor';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
|
||||
import { useDocTableContentStore } from '../stores';
|
||||
import { HeadingBlock } from '../types';
|
||||
|
||||
import { Heading } from './Heading';
|
||||
|
||||
const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return content.reduce((acc, content) => {
|
||||
if (content.type === 'text') {
|
||||
return acc + content.text;
|
||||
} else if (content.type === 'link') {
|
||||
return acc + recursiveTextContent(content.content);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, '');
|
||||
};
|
||||
|
||||
type HeadingBlock = {
|
||||
id: string;
|
||||
type: string;
|
||||
text: string;
|
||||
content: HeadingBlock[];
|
||||
contentText: string;
|
||||
props: {
|
||||
level: number;
|
||||
};
|
||||
};
|
||||
|
||||
interface TableContentProps {
|
||||
doc: Doc;
|
||||
headings: HeadingBlock[];
|
||||
}
|
||||
|
||||
export const TableContent = ({ doc }: TableContentProps) => {
|
||||
export const TableContent = ({ doc, headings }: TableContentProps) => {
|
||||
const { docsStore } = useDocStore();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const editor = docsStore?.[doc.id]?.editor;
|
||||
const headingFiltering = useCallback(
|
||||
() =>
|
||||
editor?.document
|
||||
.filter((block) => block.type === 'heading')
|
||||
.map((block) => ({
|
||||
...block,
|
||||
contentText: recursiveTextContent(
|
||||
block.content as unknown as HeadingBlock['content'],
|
||||
),
|
||||
})) as unknown as HeadingBlock[],
|
||||
[editor?.document],
|
||||
);
|
||||
|
||||
const [headings, setHeadings] = useState<HeadingBlock[]>();
|
||||
const { setIsPanelTableContentOpen, isPanelTableContentOpen } =
|
||||
useDocTableContentStore();
|
||||
const [hasBeenClose, setHasBeenClose] = useState(false);
|
||||
const setClosePanel = () => {
|
||||
setHasBeenClose(true);
|
||||
setIsPanelTableContentOpen(false);
|
||||
};
|
||||
|
||||
const [headingIdHighlight, setHeadingIdHighlight] = useState<string>();
|
||||
|
||||
// Open the panel if there are more than 1 heading
|
||||
useEffect(() => {
|
||||
if (headings?.length && headings.length > 1 && !hasBeenClose) {
|
||||
setIsPanelTableContentOpen(true);
|
||||
}
|
||||
}, [setIsPanelTableContentOpen, headings, hasBeenClose]);
|
||||
|
||||
// Close the panel unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setIsPanelTableContentOpen(false);
|
||||
};
|
||||
}, [setIsPanelTableContentOpen]);
|
||||
|
||||
// To highlight the first heading in the viewport
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -130,68 +66,57 @@ export const TableContent = ({ doc }: TableContentProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update the headings when the editor content changes
|
||||
editor?.onEditorContentChange(() => {
|
||||
setHeadings(headingFiltering());
|
||||
});
|
||||
|
||||
if (!isPanelTableContentOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel setIsPanelOpen={setClosePanel}>
|
||||
<Box $padding="small" $maxHeight="95%">
|
||||
<Box $overflow="auto">
|
||||
{headings?.map((heading) => (
|
||||
<Heading
|
||||
editor={editor}
|
||||
headingId={heading.id}
|
||||
level={heading.props.level}
|
||||
text={heading.contentText}
|
||||
key={heading.id}
|
||||
isHighlight={headingIdHighlight === heading.id}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
$height="1px"
|
||||
$width="auto"
|
||||
$background="#e5e5e5"
|
||||
$margin={{ vertical: 'small' }}
|
||||
$css="flex: none;"
|
||||
/>
|
||||
<BoxButton
|
||||
onClick={() => {
|
||||
editor.focus();
|
||||
document.querySelector(`.bn-editor`)?.scrollIntoView({
|
||||
<Box $padding={{ all: 'small', right: 'none' }} $maxHeight="95%">
|
||||
<Box $overflow="auto">
|
||||
{headings?.map((heading) => (
|
||||
<Heading
|
||||
editor={editor}
|
||||
headingId={heading.id}
|
||||
level={heading.props.level}
|
||||
text={heading.contentText}
|
||||
key={heading.id}
|
||||
isHighlight={headingIdHighlight === heading.id}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
$height="1px"
|
||||
$width="auto"
|
||||
$background="#e5e5e5"
|
||||
$margin={{ vertical: 'small' }}
|
||||
$css="flex: none;"
|
||||
/>
|
||||
<BoxButton
|
||||
onClick={() => {
|
||||
editor.focus();
|
||||
document.querySelector(`.bn-editor`)?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Back to top')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
<BoxButton
|
||||
onClick={() => {
|
||||
editor.focus();
|
||||
document
|
||||
.querySelector(
|
||||
`.bn-editor > .bn-block-group > .bn-block-outer:last-child`,
|
||||
)
|
||||
?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Back to top')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
<BoxButton
|
||||
onClick={() => {
|
||||
editor.focus();
|
||||
document
|
||||
.querySelector(
|
||||
`.bn-editor > .bn-block-group > .bn-block-outer:last-child`,
|
||||
)
|
||||
?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Go to bottom')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
</Panel>
|
||||
}}
|
||||
>
|
||||
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
|
||||
{t('Go to bottom')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './useHeading';
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useDocStore } from '../../doc-editor';
|
||||
import { HeadingBlock } from '../types';
|
||||
|
||||
const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return content.reduce((acc, content) => {
|
||||
if (content.type === 'text') {
|
||||
return acc + content.text;
|
||||
} else if (content.type === 'link') {
|
||||
return acc + recursiveTextContent(content.content);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, '');
|
||||
};
|
||||
|
||||
export const useHeading = (docId: string) => {
|
||||
const { docsStore } = useDocStore();
|
||||
const editor = docsStore?.[docId]?.editor;
|
||||
|
||||
const headingFiltering = useCallback(
|
||||
() =>
|
||||
editor?.document
|
||||
.filter((block) => block.type === 'heading')
|
||||
.map((block) => ({
|
||||
...block,
|
||||
contentText: recursiveTextContent(
|
||||
block.content as unknown as HeadingBlock['content'],
|
||||
),
|
||||
})) as unknown as HeadingBlock[],
|
||||
[editor?.document],
|
||||
);
|
||||
|
||||
const [headings, setHeadings] = useState<HeadingBlock[]>(headingFiltering());
|
||||
|
||||
editor?.onEditorContentChange(() => {
|
||||
setHeadings(headingFiltering());
|
||||
});
|
||||
|
||||
return headings;
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './components';
|
||||
export * from './stores';
|
||||
export * from './hooks';
|
||||
export * from './types';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './useDocTableContentStore';
|
||||
@@ -1,15 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface UseDocTableContentStore {
|
||||
isPanelTableContentOpen: boolean;
|
||||
setIsPanelTableContentOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const useDocTableContentStore = create<UseDocTableContentStore>(
|
||||
(set) => ({
|
||||
isPanelTableContentOpen: false,
|
||||
setIsPanelTableContentOpen: (isPanelTableContentOpen) => {
|
||||
set(() => ({ isPanelTableContentOpen }));
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
export type HeadingBlock = {
|
||||
id: string;
|
||||
type: string;
|
||||
text: string;
|
||||
content: HeadingBlock[];
|
||||
contentText: string;
|
||||
props: {
|
||||
level: number;
|
||||
};
|
||||
};
|
||||
@@ -90,6 +90,8 @@ export const ModalVersion = ({
|
||||
id: docId,
|
||||
content: newDoc,
|
||||
});
|
||||
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('Restore')}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './api';
|
||||
export * from './components';
|
||||
export * from './stores';
|
||||
export * from './types';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './useDocVersionStore';
|
||||
@@ -1,13 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface UseDocVersionStore {
|
||||
isPanelVersionOpen: boolean;
|
||||
setIsPanelVersionOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const useDocVersionStore = create<UseDocVersionStore>((set) => ({
|
||||
isPanelVersionOpen: false,
|
||||
setIsPanelVersionOpen: (isPanelVersionOpen) => {
|
||||
set(() => ({ isPanelVersionOpen }));
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user