(frontend) add word count to doc header toolbox

We can now see the word count in the doc header toolbox.
This commit is contained in:
Anthony LC
2026-07-31 10:21:16 +02:00
parent eed828d8c7
commit 6107fbb01e
5 changed files with 41 additions and 3 deletions
+1
View File
@@ -10,6 +10,7 @@ and this project adheres to
- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486
- ✨(frontend) add word count to doc header toolbox #2549
### Fixed
@@ -69,6 +69,8 @@ test.describe('Doc Header', () => {
}) => {
await createDoc(page, 'doc-update', browserName, 1);
await writeInEditor({ page, text: 'Hello Content' });
const card = page.getByLabel(
'It is the card information about the document.',
);
@@ -93,6 +95,9 @@ test.describe('Doc Header', () => {
await expect(
page.getByRole('menuitem', { name: 'Download' }),
).toBeVisible();
await expect(
page.getByRole('menuitem', { name: 'Word count: 2 words' }),
).toBeVisible();
});
test('it updates the title doc and check the broadcast', async ({
@@ -1,5 +1,15 @@
import { DocsBlockNoteEditor } from './types';
const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
export const getWordCount = (editor?: DocsBlockNoteEditor): number => {
const doc = editor?._tiptapEditor?.state.doc;
const text = doc ? doc.textBetween(0, doc.content.size, '\n') : '';
const trimmed = text.trim();
return trimmed ? trimmed.split(/\s+/).length : 0;
};
export const sanitizeColor = (color: string): string => {
return HEX_COLOR_REGEX.test(color) ? color : randomColor();
};
@@ -7,7 +7,7 @@ import {
import { Present } from '@gouvfr-lasuite/ui-kit/icons';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import AddLinkSVG from '@/assets/icons/ui-kit/add_link.svg';
@@ -21,6 +21,8 @@ import KeepOffSVG from '@/assets/icons/ui-kit/keep_off.svg';
import LeaveSVG from '@/assets/icons/ui-kit/leave.svg';
import MarkdownCopySVG from '@/assets/icons/ui-kit/markdown_copy.svg';
import MoreSVG from '@/assets/icons/ui-kit/more_horiz.svg';
import { useEditorStore } from '@/docs/doc-editor/stores/useEditorStore';
import { getWordCount } from '@/docs/doc-editor/utils';
import {
Doc,
KEY_DOC,
@@ -102,17 +104,23 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const [isModalHistoryOpen, setIsModalHistoryOpen] = useState(false);
const [isModalLeaveOpen, setIsModalLeaveOpen] = useState(false);
const { editor } = useEditorStore();
const wordCount = useMemo(
() => (openDropdown ? getWordCount(editor) : 0),
[openDropdown, editor],
);
const { restoreFocus, addLastFocus } = useFocusStore();
const { isMobile } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);
// Deep-link (#2397) and slide/URL sync live in PresenterRoot; here we only
// trigger the manual "Present" action.
const openPresenter = usePresenterStore((state) => state.open);
const { mutate: duplicateDoc } = useDuplicateDoc({
onSuccess: (data) => {
void router.push(`/docs/${data.id}`);
},
});
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalidQueries: [KEY_LIST_DOC, KEY_DOC, KEY_LIST_FAVORITE_DOC],
});
@@ -222,6 +230,17 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
},
isHidden: !doc.abilities.destroy,
},
{
type: 'separator',
},
{
label: '',
subText: t('Word count: {{count}} words', {
count: wordCount,
description:
'In the document options menu, showing the number of words in the document.',
}),
},
];
return (
@@ -895,6 +895,9 @@
"Shared with {{count}} users_many": "Shared with {{count}} users",
"Shared with {{count}} users_one": "Shared with {{count}} user",
"Shared with {{count}} users_other": "Shared with {{count}} users",
"Word count: {{count}} words_one": "Word count: {{count}} word",
"Word count: {{count}} words_many": "Word count: {{count}} words",
"Word count: {{count}} words_other": "Word count: {{count}} words",
"days_many": "days",
"days_one": "day",
"days_other": "days"