(frontend) first heading is the title of the document

When the title of the doc is not set, the first heading
is used as the title of the document.
This commit is contained in:
Anthony LC
2024-10-02 11:25:01 +02:00
committed by Anthony LC
parent 968a1383f7
commit 883055b5fb
4 changed files with 141 additions and 66 deletions

View File

@@ -40,19 +40,18 @@ test.describe('Doc Editor', () => {
await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page
.locator('.ProseMirror.bn-editor')
.fill('[test markdown](http://test-markdown.html)');
const editor = page.locator('.ProseMirror');
await editor.click();
await editor.fill('[test markdown](http://test-markdown.html)');
await expect(page.getByText('[test markdown]')).toBeVisible();
await expect(editor.getByText('[test markdown]')).toBeVisible();
await page.getByText('[test markdown]').dblclick();
await editor.getByText('[test markdown]').dblclick();
await page.locator('button[data-test="convertMarkdown"]').click();
await expect(page.getByText('[test markdown]')).toBeHidden();
await expect(editor.getByText('[test markdown]')).toBeHidden();
await expect(
page.getByRole('link', {
editor.getByRole('link', {
name: 'test markdown',
}),
).toHaveAttribute('href', 'http://test-markdown.html');
@@ -64,38 +63,40 @@ test.describe('Doc Editor', () => {
// Check the first doc
const firstDoc = await goToGridDoc(page);
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1');
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
const editor = page.locator('.ProseMirror');
await editor.click();
await editor.fill('Hello World Doc 1');
await expect(editor.getByText('Hello World Doc 1')).toBeVisible();
// Check the second doc
const secondDoc = await goToGridDoc(page, {
nthRow: 2,
});
await expect(page.locator('h2').getByText(secondDoc)).toBeVisible();
await expect(page.getByText('Hello World Doc 1')).toBeHidden();
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2');
await expect(page.getByText('Hello World Doc 2')).toBeVisible();
await expect(editor.getByText('Hello World Doc 1')).toBeHidden();
await editor.click();
await editor.fill('Hello World Doc 2');
await expect(editor.getByText('Hello World Doc 2')).toBeVisible();
// Check the first doc again
await goToGridDoc(page, {
title: firstDoc,
});
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await expect(page.getByText('Hello World Doc 2')).toBeHidden();
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
await expect(editor.getByText('Hello World Doc 2')).toBeHidden();
await expect(editor.getByText('Hello World Doc 1')).toBeVisible();
});
test('it saves the doc when we change pages', async ({ page }) => {
// Check the first doc
const doc = await goToGridDoc(page);
await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page
.locator('.ProseMirror.bn-editor')
.fill('Hello World Doc persisted 1');
await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
const editor = page.locator('.ProseMirror');
await editor.click();
await editor.fill('Hello World Doc persisted 1');
await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible();
const secondDoc = await goToGridDoc(page, {
nthRow: 2,
@@ -107,7 +108,7 @@ test.describe('Doc Editor', () => {
title: doc,
});
await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible();
});
test('it saves the doc when we quit pages', async ({ page, browserName }) => {
@@ -117,11 +118,11 @@ test.describe('Doc Editor', () => {
// Check the first doc
const doc = await goToGridDoc(page);
await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page
.locator('.ProseMirror.bn-editor')
.fill('Hello World Doc persisted 2');
await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
const editor = page.locator('.ProseMirror');
await editor.click();
await editor.fill('Hello World Doc persisted 2');
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
await page.goto('/');
@@ -129,7 +130,7 @@ test.describe('Doc Editor', () => {
title: doc,
});
await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
});
test('it cannot edit if viewer', async ({ page }) => {

View File

@@ -69,13 +69,64 @@ test.describe('Doc Header', () => {
const [randomDoc] = await createDoc(page, 'doc-update', browserName, 1);
await page.getByRole('heading', { name: randomDoc }).fill(' ');
await page.getByText('Created at ').click();
await page.getByText('Created at').click();
await expect(
page.getByRole('heading', { name: 'Untitled document' }),
).toBeVisible();
});
test('it updates the title doc from editor heading', async ({ page }) => {
await page
.getByRole('button', {
name: 'Create a new document',
})
.click();
const docHeader = page.getByLabel(
'It is the card information about the document.',
);
await expect(
docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }),
).toBeVisible();
const editor = page.locator('.ProseMirror');
await editor.locator('h1').click();
await page.keyboard.type('Hello World', { delay: 100 });
await expect(
docHeader.getByRole('heading', { name: 'Hello World', level: 2 }),
).toBeVisible();
await expect(
page.getByText('Document title updated successfully'),
).toBeVisible();
await docHeader
.getByRole('heading', { name: 'Hello World', level: 2 })
.fill('Top World');
await editor.locator('h1').fill('Super World');
await expect(
docHeader.getByRole('heading', { name: 'Top World', level: 2 }),
).toBeVisible();
await editor.locator('h1').fill('');
await docHeader
.getByRole('heading', { name: 'Top World', level: 2 })
.fill(' ');
await page.getByText('Created at').click();
await expect(
docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }),
).toBeVisible();
});
test('it deletes the doc', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1);
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();

View File

@@ -105,7 +105,8 @@ test.describe('Doc Version', () => {
title: randomDoc,
});
await expect(page.getByText('Hello')).toBeVisible();
const editor = page.locator('.ProseMirror');
await expect(editor.getByText('Hello')).toBeVisible();
await page.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('World');
@@ -153,23 +154,24 @@ test.describe('Doc Version', () => {
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.locator('.bn-block-outer').last().click();
await page.locator('.bn-block-outer').last().fill('Hello');
const editor = page.locator('.ProseMirror');
await editor.locator('.bn-block-outer').last().click();
await editor.locator('.bn-block-outer').last().fill('Hello');
await goToGridDoc(page, {
title: randomDoc,
});
await expect(page.getByText('Hello')).toBeVisible();
await page.locator('.bn-block-outer').last().click();
await expect(editor.getByText('Hello')).toBeVisible();
await editor.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('World');
await editor.locator('.bn-block-outer').last().fill('World');
await goToGridDoc(page, {
title: randomDoc,
});
await expect(page.getByText('World')).toBeVisible();
await expect(editor.getByText('World')).toBeVisible();
await page.getByLabel('Open the document options').click();
await page
@@ -180,7 +182,7 @@ test.describe('Doc Version', () => {
const panel = page.getByLabel('Document panel');
await panel.locator('li').nth(1).click();
await expect(page.getByText('World')).toBeHidden();
await expect(editor.getByText('World')).toBeHidden();
await page
.getByRole('button', {
@@ -199,7 +201,7 @@ test.describe('Doc Version', () => {
await expect(panel.locator('li')).toHaveCount(3);
await panel.getByText('Current version').click();
await expect(page.getByText('Hello')).toBeVisible();
await expect(page.getByText('World')).toBeHidden();
await expect(editor.getByText('Hello')).toBeVisible();
await expect(editor.getByText('World')).toBeHidden();
});
});

View File

@@ -5,12 +5,13 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import React, { useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useHeadingStore } from '@/features/docs/doc-editor';
import {
Doc,
KEY_DOC,
@@ -49,38 +50,41 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
const { toast } = useToastProvider();
const { untitledDocument } = useTrans();
const isUntitled = titleDisplay === untitledDocument;
const { headings } = useHeadingStore();
const headingText = headings?.[0]?.contentText;
const debounceRef = useRef<NodeJS.Timeout>();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
onSuccess(data) {
if (data.title !== untitledDocument) {
toast(t('Document title updated successfully'), VariantType.SUCCESS);
}
},
});
const handleTitleSubmit = (inputText: string) => {
let sanitizedTitle = inputText.trim();
sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, '');
const handleTitleSubmit = useCallback(
(inputText: string) => {
let sanitizedTitle = inputText.trim();
sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, '');
// When blank we set to untitled
if (!sanitizedTitle) {
sanitizedTitle = untitledDocument;
setTitleDisplay(sanitizedTitle);
}
// When blank we set to untitled
if (!sanitizedTitle) {
sanitizedTitle = untitledDocument;
setTitleDisplay(sanitizedTitle);
}
// If mutation we update
if (sanitizedTitle !== doc.title) {
updateDoc(
{ id: doc.id, title: sanitizedTitle },
{
onSuccess: () => {
if (sanitizedTitle !== untitledDocument) {
toast(
t('Document title updated successfully'),
VariantType.SUCCESS,
);
}
},
},
);
}
};
// If mutation we update
if (sanitizedTitle !== doc.title) {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
debounceRef.current = undefined;
}
updateDoc({ id: doc.id, title: sanitizedTitle });
}
},
[doc.id, doc.title, untitledDocument, updateDoc],
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
@@ -95,6 +99,23 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
}
};
useEffect(() => {
if ((!debounceRef.current && !isUntitled) || !headingText) {
return;
}
setTitleDisplay(headingText);
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
debounceRef.current = setTimeout(() => {
handleTitleSubmit(headingText);
debounceRef.current = undefined;
}, 3000);
}, [isUntitled, handleTitleSubmit, headingText]);
return (
<>
<DocTitleStyle />