✨(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:
@@ -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 }) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user