♻️(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 useSaveDoc from '../hook/useSaveDoc';
|
||||
import { useDocStore } from '../stores';
|
||||
import { useDocStore, useHeadingStore } from '../stores';
|
||||
import { randomColor } from '../utils';
|
||||
|
||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||
@@ -78,6 +78,7 @@ export const BlockNoteContent = ({
|
||||
isError: isErrorAttachment,
|
||||
error: errorAttachment,
|
||||
} = useCreateDocAttachment();
|
||||
const { setHeadings, resetHeadings } = useHeadingStore();
|
||||
|
||||
const uploadFile = useCallback(
|
||||
async (file: File) => {
|
||||
@@ -116,6 +117,18 @@ export const BlockNoteContent = ({
|
||||
setStore(storeId, { editor });
|
||||
}, [setStore, storeId, editor]);
|
||||
|
||||
useEffect(() => {
|
||||
setHeadings(editor);
|
||||
|
||||
editor?.onEditorContentChange(() => {
|
||||
setHeadings(editor);
|
||||
});
|
||||
|
||||
return () => {
|
||||
resetHeadings();
|
||||
};
|
||||
}, [editor, resetHeadings, setHeadings]);
|
||||
|
||||
return (
|
||||
<Box $css={cssEditor}>
|
||||
{isErrorAttachment && (
|
||||
|
||||
@@ -8,9 +8,10 @@ import { Box, Card, Text, TextErrors } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { DocHeader } from '@/features/docs/doc-header';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { useHeading } from '@/features/docs/doc-table-content';
|
||||
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
||||
|
||||
import { useHeadingStore } from '../stores';
|
||||
|
||||
import { BlockNoteEditor } from './BlockNoteEditor';
|
||||
import { IconOpenPanelEditor, PanelEditor } from './PanelEditor';
|
||||
|
||||
@@ -23,7 +24,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
||||
query: { versionId },
|
||||
} = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const headings = useHeading(doc.id);
|
||||
const { headings } = useHeadingStore();
|
||||
|
||||
const isVersion = versionId && typeof versionId === 'string';
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ 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 { Doc } from '@/features/docs/doc-management';
|
||||
import { TableContent } from '@/features/docs/doc-table-content';
|
||||
import { VersionList } from '@/features/docs/doc-versioning';
|
||||
|
||||
import { usePanelEditorStore } from '../stores/usePanelEditorStore';
|
||||
import { usePanelEditorStore } from '../stores';
|
||||
import { HeadingBlock } from '../types';
|
||||
|
||||
interface PanelProps {
|
||||
doc: Doc;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './components';
|
||||
export * from './stores';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useDocStore';
|
||||
export * from './useHeadingStore';
|
||||
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 {
|
||||
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 { 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 { HeadingBlock } from '../types';
|
||||
|
||||
import { Heading } from './Heading';
|
||||
|
||||
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 './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