♻️(frontend) change useHeading to useHeadingStore
We need to get the headings in multiple places. To not have multiple listeners to compute the same thing, we will use a store to store the editor headings.
This commit is contained in:
@@ -13,7 +13,7 @@ import { Version } from '@/features/docs/doc-versioning/';
|
|||||||
|
|
||||||
import { useCreateDocAttachment } from '../api/useCreateDocUpload';
|
import { useCreateDocAttachment } from '../api/useCreateDocUpload';
|
||||||
import useSaveDoc from '../hook/useSaveDoc';
|
import useSaveDoc from '../hook/useSaveDoc';
|
||||||
import { useDocStore } from '../stores';
|
import { useDocStore, useHeadingStore } from '../stores';
|
||||||
import { randomColor } from '../utils';
|
import { randomColor } from '../utils';
|
||||||
|
|
||||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||||
@@ -78,6 +78,7 @@ export const BlockNoteContent = ({
|
|||||||
isError: isErrorAttachment,
|
isError: isErrorAttachment,
|
||||||
error: errorAttachment,
|
error: errorAttachment,
|
||||||
} = useCreateDocAttachment();
|
} = useCreateDocAttachment();
|
||||||
|
const { setHeadings, resetHeadings } = useHeadingStore();
|
||||||
|
|
||||||
const uploadFile = useCallback(
|
const uploadFile = useCallback(
|
||||||
async (file: File) => {
|
async (file: File) => {
|
||||||
@@ -116,6 +117,18 @@ export const BlockNoteContent = ({
|
|||||||
setStore(storeId, { editor });
|
setStore(storeId, { editor });
|
||||||
}, [setStore, storeId, editor]);
|
}, [setStore, storeId, editor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHeadings(editor);
|
||||||
|
|
||||||
|
editor?.onEditorContentChange(() => {
|
||||||
|
setHeadings(editor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resetHeadings();
|
||||||
|
};
|
||||||
|
}, [editor, resetHeadings, setHeadings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box $css={cssEditor}>
|
<Box $css={cssEditor}>
|
||||||
{isErrorAttachment && (
|
{isErrorAttachment && (
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { Box, Card, Text, TextErrors } from '@/components';
|
|||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { DocHeader } from '@/features/docs/doc-header';
|
import { DocHeader } from '@/features/docs/doc-header';
|
||||||
import { Doc } from '@/features/docs/doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { useHeading } from '@/features/docs/doc-table-content';
|
|
||||||
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
||||||
|
|
||||||
|
import { useHeadingStore } from '../stores';
|
||||||
|
|
||||||
import { BlockNoteEditor } from './BlockNoteEditor';
|
import { BlockNoteEditor } from './BlockNoteEditor';
|
||||||
import { IconOpenPanelEditor, PanelEditor } from './PanelEditor';
|
import { IconOpenPanelEditor, PanelEditor } from './PanelEditor';
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
|||||||
query: { versionId },
|
query: { versionId },
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const headings = useHeading(doc.id);
|
const { headings } = useHeadingStore();
|
||||||
|
|
||||||
const isVersion = versionId && typeof versionId === 'string';
|
const isVersion = versionId && typeof versionId === 'string';
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { Box, BoxButton, Card, IconBG, Text } from '@/components';
|
import { Box, BoxButton, Card, IconBG, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc } from '@/features/docs//doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { HeadingBlock, TableContent } from '@/features/docs/doc-table-content';
|
import { TableContent } from '@/features/docs/doc-table-content';
|
||||||
import { VersionList } from '@/features/docs/doc-versioning';
|
import { VersionList } from '@/features/docs/doc-versioning';
|
||||||
|
|
||||||
import { usePanelEditorStore } from '../stores/usePanelEditorStore';
|
import { usePanelEditorStore } from '../stores';
|
||||||
|
import { HeadingBlock } from '../types';
|
||||||
|
|
||||||
interface PanelProps {
|
interface PanelProps {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './stores';
|
export * from './stores';
|
||||||
|
export * from './types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './useDocStore';
|
export * from './useDocStore';
|
||||||
|
export * from './useHeadingStore';
|
||||||
export * from './usePanelEditorStore';
|
export * from './usePanelEditorStore';
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { BlockNoteEditor } from '@blocknote/core';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
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 interface UseHeadingStore {
|
||||||
|
headings: HeadingBlock[];
|
||||||
|
setHeadings: (editor: BlockNoteEditor) => void;
|
||||||
|
resetHeadings: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHeadingStore = create<UseHeadingStore>((set) => ({
|
||||||
|
headings: [],
|
||||||
|
setHeadings: (editor) => {
|
||||||
|
const headingBlocks = editor?.document
|
||||||
|
.filter((block) => block.type === 'heading')
|
||||||
|
.map((block) => ({
|
||||||
|
...block,
|
||||||
|
contentText: recursiveTextContent(
|
||||||
|
block.content as unknown as HeadingBlock['content'],
|
||||||
|
),
|
||||||
|
})) as unknown as HeadingBlock[];
|
||||||
|
|
||||||
|
set(() => ({ headings: headingBlocks }));
|
||||||
|
},
|
||||||
|
resetHeadings: () => set(() => ({ headings: [] })),
|
||||||
|
}));
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
export interface DocAttachment {
|
export interface DocAttachment {
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HeadingBlock = {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
text: string;
|
||||||
|
content: HeadingBlock[];
|
||||||
|
contentText: string;
|
||||||
|
props: {
|
||||||
|
level: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, BoxButton, Text } from '@/components';
|
import { Box, BoxButton, Text } from '@/components';
|
||||||
import { useDocStore } from '@/features/docs/doc-editor';
|
import { HeadingBlock, useDocStore } from '@/features/docs/doc-editor';
|
||||||
import { Doc } from '@/features/docs/doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { HeadingBlock } from '../types';
|
|
||||||
|
|
||||||
import { Heading } from './Heading';
|
import { Heading } from './Heading';
|
||||||
|
|
||||||
interface TableContentProps {
|
interface TableContentProps {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './useHeading';
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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,3 +1 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './hooks';
|
|
||||||
export * from './types';
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
export type HeadingBlock = {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
text: string;
|
|
||||||
content: HeadingBlock[];
|
|
||||||
contentText: string;
|
|
||||||
props: {
|
|
||||||
level: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user