diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6232c41..5123f2b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ and this project adheres to
- 💄(frontend) update DocsGridOptions component #432
- 💄(frontend) update DocHeader ui #446
- 💄(frontend) update doc versioning ui #463
+- 💄(frontend) update doc summary ui #473
## [1.10.0] - 2024-12-17
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts
index 5fd42f6f..9b4d8788 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts
@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
-import { createDoc, goToGridDoc, verifyDocName } from './common';
+import { createDoc, verifyDocName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -19,20 +19,13 @@ test.describe('Doc Table Content', () => {
await verifyDocName(page, randomDoc);
- await page.getByLabel('Open the document options').click();
- await page
- .getByRole('button', {
- name: 'Table of contents',
- })
- .click();
-
- const panel = page.getByLabel('Document panel');
const editor = page.locator('.ProseMirror');
await editor.locator('.bn-block-outer').last().fill('/');
+
await page.getByText('Heading 1').click();
- await page.keyboard.type('Hello World');
- await editor.getByText('Hello').dblclick();
+ await page.keyboard.type('Level 1');
+ await editor.getByText('Level 1').dblclick();
await page.getByRole('button', { name: 'Strike' }).click();
await page.locator('.bn-block-outer').first().click();
@@ -40,101 +33,44 @@ test.describe('Doc Table Content', () => {
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
- for (let i = 0; i < 10; i++) {
+ for (let i = 0; i < 2; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 2').click();
- await page.keyboard.type('Super World', { delay: 100 });
+ await page.keyboard.type('Level 2');
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
- for (let i = 0; i < 10; i++) {
+ for (let i = 0; i < 2; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 3').click();
- await page.keyboard.type('Another World');
+ await page.keyboard.type('Level 3');
- const hello = panel.getByText('Hello World');
- const superW = panel.getByText('Super World');
- const another = panel.getByText('Another World');
+ expect(true).toBe(true);
- await expect(hello).toBeVisible();
- await expect(hello).toHaveCSS('font-size', /17/);
- await expect(hello).toHaveAttribute('aria-selected', 'true');
+ const summaryContainer = page.locator('#summaryContainer');
+ await summaryContainer.hover();
- await expect(superW).toBeVisible();
- await expect(superW).toHaveCSS('font-size', /14/);
- await expect(superW).toHaveAttribute('aria-selected', 'false');
+ const level1 = summaryContainer.getByText('Level 1');
+ const level2 = summaryContainer.getByText('Level 2');
+ const level3 = summaryContainer.getByText('Level 3');
- await expect(another).toBeVisible();
- await expect(another).toHaveCSS('font-size', /12/);
- await expect(another).toHaveAttribute('aria-selected', 'false');
+ await expect(level1).toBeVisible();
+ await expect(level1).toHaveCSS('padding', /4px 0px/);
+ await expect(level1).toHaveAttribute('aria-selected', 'true');
- await hello.click();
+ await expect(level2).toBeVisible();
+ await expect(level2).toHaveCSS('padding-left', /14.4px/);
+ await expect(level2).toHaveAttribute('aria-selected', 'false');
- await expect(editor.getByText('Hello World')).toBeInViewport();
- await expect(hello).toHaveAttribute('aria-selected', 'true');
- await expect(superW).toHaveAttribute('aria-selected', 'false');
-
- await another.click();
-
- await expect(editor.getByText('Hello World')).not.toBeInViewport();
- await expect(hello).toHaveAttribute('aria-selected', 'false');
- await expect(superW).toHaveAttribute('aria-selected', 'true');
-
- await panel.getByText('Back to top').click();
- await expect(editor.getByText('Hello World')).toBeInViewport();
- await expect(hello).toHaveAttribute('aria-selected', 'true');
- await expect(superW).toHaveAttribute('aria-selected', 'false');
-
- await panel.getByText('Go to bottom').click();
- await expect(editor.getByText('Hello World')).not.toBeInViewport();
- await expect(superW).toHaveAttribute('aria-selected', 'true');
- });
-
- test('it checks that table contents panel is opened automaticaly if more that 2 headings', async ({
- page,
- browserName,
- }) => {
- const [randomDoc] = await createDoc(
- page,
- 'doc-table-content',
- browserName,
- 1,
- );
-
- await verifyDocName(page, randomDoc);
- await expect(page.getByLabel('Open the panel')).toBeHidden();
-
- const editor = page.locator('.ProseMirror');
-
- await editor.locator('.bn-block-outer').last().fill('/');
- await page.getByText('Heading 1').click();
- await page.keyboard.type('Hello World', { delay: 100 });
-
- await page.keyboard.press('Enter');
-
- await editor.locator('.bn-block-outer').last().fill('/');
- await page.getByText('Heading 2').click();
- await page.keyboard.type('Super World', { delay: 100 });
-
- await goToGridDoc(page, {
- title: randomDoc,
- });
-
- await expect(page.getByLabel('Close the panel')).toBeVisible();
-
- const panel = page.getByLabel('Document panel');
- await expect(panel.getByText('Hello World')).toBeVisible();
- await expect(panel.getByText('Super World')).toBeVisible();
-
- await page.getByLabel('Close the panel').click();
-
- await expect(panel).toHaveAttribute('aria-hidden', 'true');
+ await expect(level3).toBeVisible();
+ await expect(level3).toHaveCSS('padding-left', /24px/);
+ await expect(level3).toHaveAttribute('aria-selected', 'false');
});
});
diff --git a/src/frontend/apps/impress/src/components/separators/HorizontalSeparator.tsx b/src/frontend/apps/impress/src/components/separators/HorizontalSeparator.tsx
index b660e259..cd617985 100644
--- a/src/frontend/apps/impress/src/components/separators/HorizontalSeparator.tsx
+++ b/src/frontend/apps/impress/src/components/separators/HorizontalSeparator.tsx
@@ -9,10 +9,12 @@ export enum SeparatorVariant {
type Props = {
variant?: SeparatorVariant;
+ $withPadding?: boolean;
};
export const HorizontalSeparator = ({
variant = SeparatorVariant.LIGHT,
+ $withPadding = true,
}: Props) => {
const { colorsTokens } = useCunninghamTheme();
@@ -20,7 +22,7 @@ export const HorizontalSeparator = ({
`
&, & > .bn-container, & .ProseMirror {
height:100%
};
- & .bn-editor {
- padding-right: 30px;
- ${readonly && `padding-left: 30px;`}
- };
+
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
index fa39f405..5893c4f5 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
@@ -2,9 +2,10 @@ import { Alert, Loader, VariantType } from '@openfun/cunningham-react';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { css } from 'styled-components';
import * as Y from 'yjs';
-import { Box, Card, Text, TextErrors } from '@/components';
+import { Box, Text, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocHeader, DocVersionHeader } from '@/features/docs/doc-header/';
import {
@@ -12,11 +13,11 @@ import {
base64ToBlocknoteXmlFragment,
useProviderStore,
} from '@/features/docs/doc-management';
+import { TableContent } from '@/features/docs/doc-table-content/';
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
import { useResponsiveStore } from '@/stores';
import { BlockNoteEditor, BlockNoteEditorVersion } from './BlockNoteEditor';
-import { IconOpenPanelEditor, PanelEditor } from './PanelEditor';
interface DocEditorProps {
doc: Doc;
@@ -25,9 +26,9 @@ interface DocEditorProps {
export const DocEditor = ({ doc, versionId }: DocEditorProps) => {
const { t } = useTranslation();
- const { isMobile } = useResponsiveStore();
+ const { isDesktop } = useResponsiveStore();
- const isVersion = versionId && typeof versionId === 'string';
+ const isVersion = !!versionId && typeof versionId === 'string';
const { colorsTokens } = useCunninghamTheme();
@@ -39,41 +40,49 @@ export const DocEditor = ({ doc, versionId }: DocEditorProps) => {
return (
<>
- {isVersion ? (
-
- ) : (
-
- )}
-
- {!doc.abilities.partial_update && (
-
-
- {t(`Read only, you cannot edit this document.`)}
-
+ {isDesktop && !isVersion && (
+
+
)}
+
+
+ {isVersion ? (
+
+ ) : (
+
+ )}
+
-
-
+
+ {t(`Read only, you cannot edit this document.`)}
+
+
+ )}
+
+
- {isVersion ? (
-
- ) : (
-
- )}
- {!isMobile && !isVersion && }
-
- {!isVersion && }
+
+ {isVersion ? (
+
+ ) : (
+
+ )}
+
+
>
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx
deleted file mode 100644
index 729fe6c2..00000000
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { Box, BoxButton, Card, IconBG, Text } from '@/components';
-import { useCunninghamTheme } from '@/cunningham';
-import { TableContent } from '@/features/docs/doc-table-content';
-import { useResponsiveStore } from '@/stores';
-
-import { useHeadingStore, usePanelEditorStore } from '../stores';
-
-export const PanelEditor = () => {
- const { t } = useTranslation();
- const { colorsTokens } = useCunninghamTheme();
- const { isMobile } = useResponsiveStore();
- const { isPanelTableContentOpen, setIsPanelTableContentOpen, isPanelOpen } =
- usePanelEditorStore();
-
- return (
-
-
- {isMobile && }
-
-
- setIsPanelTableContentOpen(true)}
- $zIndex={1}
- >
-
- {t('Table of content')}
-
-
-
-
-
-
- );
-};
-
-export const IconOpenPanelEditor = () => {
- const { headings } = useHeadingStore();
- const { t } = useTranslation();
- const { setIsPanelOpen, isPanelOpen, setIsPanelTableContentOpen } =
- usePanelEditorStore();
- const [hasBeenOpen, setHasBeenOpen] = useState(isPanelOpen);
- const { isMobile } = useResponsiveStore();
-
- const setClosePanel = () => {
- setHasBeenOpen(true);
- setIsPanelOpen(!isPanelOpen);
- };
-
- // Open the panel if there are more than 1 heading
- useEffect(() => {
- if (headings?.length && headings.length > 1 && !hasBeenOpen && !isMobile) {
- setIsPanelTableContentOpen(true);
- setIsPanelOpen(true);
- setHasBeenOpen(true);
- }
- }, [
- headings,
- setIsPanelTableContentOpen,
- setIsPanelOpen,
- hasBeenOpen,
- isMobile,
- ]);
-
- // 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 (
-
- );
-};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx
index 05012188..d90026fb 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx
@@ -35,7 +35,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
<>
@@ -92,7 +92,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
-
+
>
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
index 5e92efa5..4146db10 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
@@ -5,10 +5,10 @@ import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useResponsiveStore } from '@/stores';
-const sizeMap: { [key: number]: string } = {
- 1: '1.1rem',
+const leftPaddingMap: { [key: number]: string } = {
+ 3: '1.5rem',
2: '0.9rem',
- 3: '0.8rem',
+ 1: '0.3',
};
export type HeadingsHighlight = {
@@ -34,9 +34,12 @@ export const Heading = ({
const [isHover, setIsHover] = useState(isHighlight);
const { colorsTokens } = useCunninghamTheme();
const { isMobile } = useResponsiveStore();
+ const isActive = isHighlight || isHover;
return (
setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
@@ -47,23 +50,24 @@ export const Heading = ({
}
editor.setTextCursorPosition(headingId, 'end');
+
document.querySelector(`[data-id="${headingId}"]`)?.scrollIntoView({
behavior: 'smooth',
+ inline: 'start',
block: 'start',
});
}}
+ $radius="4px"
+ $background={isActive ? `${colorsTokens()['greyscale-100']}` : 'none'}
$css="text-align: left;"
>
{text}
diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
index 5fe946b5..f271aa6d 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
@@ -1,21 +1,22 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { css } from 'styled-components';
-import { Box, BoxButton, Text } from '@/components';
+import { Box, Icon, Text } from '@/components';
import { useEditorStore, useHeadingStore } from '@/features/docs/doc-editor';
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
-import { useResponsiveStore } from '@/stores';
import { Heading } from './Heading';
export const TableContent = () => {
const { headings } = useHeadingStore();
const { editor } = useEditorStore();
- const { isMobile } = useResponsiveStore();
- const { t } = useTranslation();
+
const [headingIdHighlight, setHeadingIdHighlight] = useState();
- // To highlight the first heading in the viewport
+ const { t } = useTranslation();
+ const [isHover, setIsHover] = useState(false);
+
useEffect(() => {
const handleScroll = () => {
if (!headings) {
@@ -62,69 +63,84 @@ export const TableContent = () => {
}
return (
-
-
- {headings?.map(
- (heading) =>
- heading.contentText && (
-
- ),
- )}
-
-
- {
- // With mobile the focus open the keyboard and the scroll is not working
- if (!isMobile) {
- editor.focus();
- }
+ {
+ setIsHover(true);
+ setTimeout(() => {
+ const element = document.getElementById(
+ `heading-${headingIdHighlight}`,
+ );
- document.querySelector(`.bn-editor`)?.scrollIntoView({
+ element?.scrollIntoView({
behavior: 'smooth',
- block: 'start',
+ inline: 'center',
+ block: 'center',
});
- }}
- $align="start"
- >
-
- {t('Back to top')}
-
-
- {
- // With mobile the focus open the keyboard and the scroll is not working
- if (!isMobile) {
- editor.focus();
- }
+ }, 250); // 300ms is the transition time of the box
+ }}
+ onMouseLeave={() => {
+ setIsHover(false);
+ }}
+ id="summaryContainer"
+ $effect="show"
+ $width="40px"
+ $height="40px"
+ $zIndex={1000}
+ $align="center"
+ $padding="xs"
+ $justify="center"
+ $css={css`
+ border: 1px solid #ccc;
+ overflow: hidden;
+ border-radius: var(--c--theme--spacings--3xs);
+ background: var(--c--theme--colors--greyscale-000);
- document
- .querySelector(
- `.bn-editor > .bn-block-group > .bn-block-outer:last-child`,
- )
- ?.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- }}
- $align="start"
- >
-
- {t('Go to bottom')}
-
-
+ &:hover {
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ gap: var(--c--theme--spacings--2xs);
+ width: 200px;
+ height: auto;
+ max-height: calc(100vh - 60px - 15vh);
+ }
+ `}
+ >
+ {!isHover && (
+
+
+
+ )}
+ {isHover && (
+
+
+
+ {t('Summary')}
+
+
+
+ {headings?.map(
+ (heading) =>
+ heading.contentText && (
+
+ ),
+ )}
+
+ )}
);
};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/index.ts
index 323f1534..d1a13b2a 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/index.ts
@@ -1 +1,2 @@
export * from './TableContent';
+export * from './Heading';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalSelectVersion.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalSelectVersion.tsx
index cc0adb09..7cc79f05 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalSelectVersion.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalSelectVersion.tsx
@@ -64,7 +64,7 @@ export const ModalSelectVersion = ({
flex: 1;
`}
>
-
+
{selectedVersionId && (
)}