💄(frontend) improve ui table of contents
- keep correctly the text on the left side - improve accuracy highlightment heading when scrolling - display full heading text when text transform is applied - fix typo
This commit is contained in:
@@ -20,7 +20,7 @@ test.describe('Doc Table Content', () => {
|
|||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page
|
await page
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
name: 'Table of content',
|
name: 'Table of contents',
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ test.describe('Doc Table Content', () => {
|
|||||||
await editor.locator('.bn-block-outer').last().fill('/');
|
await editor.locator('.bn-block-outer').last().fill('/');
|
||||||
await page.getByText('Heading 1').click();
|
await page.getByText('Heading 1').click();
|
||||||
await page.keyboard.type('Hello World');
|
await page.keyboard.type('Hello World');
|
||||||
|
await editor.getByText('Hello').dblclick();
|
||||||
|
await page.getByRole('button', { name: 'Strike' }).click();
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().click();
|
await page.locator('.bn-block-outer').last().click();
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ test.describe('Doc Table Content', () => {
|
|||||||
|
|
||||||
await editor.locator('.bn-block-outer').last().fill('/');
|
await editor.locator('.bn-block-outer').last().fill('/');
|
||||||
await page.getByText('Heading 2').click();
|
await page.getByText('Heading 2').click();
|
||||||
await page.keyboard.type('Super World');
|
await page.keyboard.type('Super World', { delay: 100 });
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().click();
|
await page.locator('.bn-block-outer').last().click();
|
||||||
|
|
||||||
@@ -58,15 +60,15 @@ test.describe('Doc Table Content', () => {
|
|||||||
const another = panel.getByText('Another World');
|
const another = panel.getByText('Another World');
|
||||||
|
|
||||||
await expect(hello).toBeVisible();
|
await expect(hello).toBeVisible();
|
||||||
await expect(hello).toHaveCSS('font-size', '19.2px');
|
await expect(hello).toHaveCSS('font-size', /19/);
|
||||||
await expect(hello).toHaveAttribute('aria-selected', 'true');
|
await expect(hello).toHaveAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
await expect(superW).toBeVisible();
|
await expect(superW).toBeVisible();
|
||||||
await expect(superW).toHaveCSS('font-size', '16px');
|
await expect(superW).toHaveCSS('font-size', /16/);
|
||||||
await expect(superW).toHaveAttribute('aria-selected', 'false');
|
await expect(superW).toHaveAttribute('aria-selected', 'false');
|
||||||
|
|
||||||
await expect(another).toBeVisible();
|
await expect(another).toBeVisible();
|
||||||
await expect(another).toHaveCSS('font-size', '12.8px');
|
await expect(another).toHaveCSS('font-size', /12/);
|
||||||
await expect(another).toHaveAttribute('aria-selected', 'false');
|
await expect(another).toHaveAttribute('aria-selected', 'false');
|
||||||
|
|
||||||
await hello.click();
|
await hello.click();
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ export const Panel = ({
|
|||||||
$width="100%"
|
$width="100%"
|
||||||
$maxWidth="20rem"
|
$maxWidth="20rem"
|
||||||
$position="sticky"
|
$position="sticky"
|
||||||
$maxHeight="96vh"
|
$maxHeight="99vh"
|
||||||
$height="100%"
|
$height="100%"
|
||||||
$css={`
|
$css={`
|
||||||
top: 2vh;
|
top: 0vh;
|
||||||
transition: ${transition};
|
transition: ${transition};
|
||||||
${
|
${
|
||||||
!isOpen &&
|
!isOpen &&
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
icon={<span className="material-icons">summarize</span>}
|
icon={<span className="material-icons">summarize</span>}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
<Text $theme="primary">{t('Table of content')}</Text>
|
<Text $theme="primary">{t('Table of contents')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const Heading = ({
|
|||||||
block: 'start',
|
block: 'start',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
$css="text-align: left;"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
$theme="primary"
|
$theme="primary"
|
||||||
|
|||||||
@@ -10,11 +10,28 @@ import { useDocTableContentStore } from '../stores';
|
|||||||
|
|
||||||
import { Heading } from './Heading';
|
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 = {
|
type HeadingBlock = {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
text: string;
|
text: string;
|
||||||
content: HeadingBlock[];
|
content: HeadingBlock[];
|
||||||
|
contentText: string;
|
||||||
props: {
|
props: {
|
||||||
level: number;
|
level: number;
|
||||||
};
|
};
|
||||||
@@ -31,9 +48,14 @@ export const TableContent = ({ doc }: TableContentProps) => {
|
|||||||
const editor = docsStore?.[doc.id]?.editor;
|
const editor = docsStore?.[doc.id]?.editor;
|
||||||
const headingFiltering = useCallback(
|
const headingFiltering = useCallback(
|
||||||
() =>
|
() =>
|
||||||
editor?.document.filter(
|
editor?.document
|
||||||
(block) => block.type === 'heading',
|
.filter((block) => block.type === 'heading')
|
||||||
) as unknown as HeadingBlock[],
|
.map((block) => ({
|
||||||
|
...block,
|
||||||
|
contentText: recursiveTextContent(
|
||||||
|
block.content as unknown as HeadingBlock['content'],
|
||||||
|
),
|
||||||
|
})) as unknown as HeadingBlock[],
|
||||||
[editor?.document],
|
[editor?.document],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,7 +93,7 @@ export const TableContent = ({ doc }: TableContentProps) => {
|
|||||||
|
|
||||||
for (const heading of headings) {
|
for (const heading of headings) {
|
||||||
const elHeading = document.body.querySelector(
|
const elHeading = document.body.querySelector(
|
||||||
`.bn-block-outer[data-id="${heading.id}"]`,
|
`.bn-block-outer[data-id="${heading.id}"] [data-content-type="heading"]:first-child`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!elHeading) {
|
if (!elHeading) {
|
||||||
@@ -121,21 +143,16 @@ export const TableContent = ({ doc }: TableContentProps) => {
|
|||||||
<Panel setIsPanelOpen={setClosePanel}>
|
<Panel setIsPanelOpen={setClosePanel}>
|
||||||
<Box $padding="small" $maxHeight="95%">
|
<Box $padding="small" $maxHeight="95%">
|
||||||
<Box $overflow="auto">
|
<Box $overflow="auto">
|
||||||
{headings?.map((heading) => {
|
{headings?.map((heading) => (
|
||||||
const content = heading.content?.[0];
|
<Heading
|
||||||
const text = content?.type === 'text' ? content.text : '';
|
editor={editor}
|
||||||
|
headingId={heading.id}
|
||||||
return (
|
level={heading.props.level}
|
||||||
<Heading
|
text={heading.contentText}
|
||||||
editor={editor}
|
key={heading.id}
|
||||||
headingId={heading.id}
|
isHighlight={headingIdHighlight === heading.id}
|
||||||
level={heading.props.level}
|
/>
|
||||||
text={text}
|
))}
|
||||||
key={heading.id}
|
|
||||||
isHighlight={headingIdHighlight === heading.id}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
$height="1px"
|
$height="1px"
|
||||||
|
|||||||
Reference in New Issue
Block a user