diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3b5e6f02..ce005cae19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to - ♿(frontend) improve accessibility: - ♿(frontend) improve share modal button accessibility #1626 +- 🐛(frontend) prevent duplicate as first character in title #1595 + ## [3.10.0] - 2025-11-18 @@ -46,7 +48,6 @@ and this project adheres to - 🔥(backend) remove api managing templates - ## [3.9.0] - 2025-11-10 ### Added diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index 99635176fd..1b748705e2 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -89,8 +89,8 @@ test.describe('Doc Header', () => { await verifyDocName(page, docChild); - // Emoji picker should be hidden initially - await expect(emojiPicker).toBeHidden(); + // Emoji picker is now always visible for sub-docs (button shows default icon) + await expect(emojiPicker).toBeVisible(); // Add emoji await optionMenu.click(); @@ -119,7 +119,8 @@ test.describe('Doc Header', () => { await optionMenu.click(); await expect(addEmojiMenuItem).toBeHidden(); await removeEmojiMenuItem.click(); - await expect(emojiPicker).toBeHidden(); + // Emoji button remains visible even after removing the emoji + await expect(emojiPicker).toBeVisible(); }); test('it deletes the doc', async ({ page, browserName }) => { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts index 0e3bf49cc5..4c313407d8 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts @@ -352,7 +352,8 @@ test.describe('Doc Tree', () => { await page.getByRole('menuitem', { name: 'Remove emoji' }).click(); await expect(row.getByText('😀')).toBeHidden(); - await expect(titleEmojiPicker).toBeHidden(); + // Emoji button remains visible in the header even with no icon set + await expect(titleEmojiPicker).toBeVisible(); }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx index 33905caa73..23a55f6978 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx @@ -55,10 +55,6 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => { const { colorsTokens } = useCunninghamTheme(); const { emoji } = getEmojiAndTitle(doc.title ?? ''); - if (!emoji) { - return null; - } - return ( { if (isTopRoot) { const sanitizedTitle = updateDocTitle(doc, inputText); setTitleDisplay(sanitizedTitle); + return sanitizedTitle; } else { - const sanitizedTitle = updateDocTitle( - doc, - emoji ? `${emoji} ${inputText}` : inputText, - ); + const { emoji: pastedEmoji } = getEmojiAndTitle(inputText); + const textPreservingPastedEmoji = pastedEmoji + ? `\u200B${inputText}` + : inputText; + const finalTitle = emoji + ? `${emoji} ${textPreservingPastedEmoji}` + : textPreservingPastedEmoji; + + const sanitizedTitle = updateDocTitle(doc, finalTitle); const { titleWithoutEmoji: sanitizedTitleWithoutEmoji } = getEmojiAndTitle(sanitizedTitle); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts index 2d43b68fc2..c126cc8a4a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/utils.ts @@ -26,12 +26,13 @@ export const getEmojiAndTitle = (title: string) => { // Use emoji-regex library for comprehensive emoji detection compatible with ES5 const regex = emojiRegex(); - // Check if the title starts with an emoji - const match = title.match(regex); + // Ignore leading spaces when checking for a leading emoji + const trimmedTitle = title.trimStart(); + const match = trimmedTitle.match(regex); - if (match && title.startsWith(match[0])) { + if (match && trimmedTitle.startsWith(match[0])) { const emoji = match[0]; - const titleWithoutEmoji = title.substring(emoji.length).trim(); + const titleWithoutEmoji = trimmedTitle.substring(emoji.length).trim(); return { emoji, titleWithoutEmoji }; }