Skip to content

Commit 5e4f415

Browse files
committed
✨(frontend) prevent duplicate emoji when used as first character in title
ensures icon and title are visually distinct in sub-document headers Signed-off-by: Cyril <c.gromoff@gmail.com>
1 parent 6314cb3 commit 5e4f415

File tree

3 files changed

+32
-12
lines changed

3 files changed

+32
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ and this project adheres to
1919
- 🐛(docx) fix image overflow by limiting width to 600px during export #1525
2020
- 🐛(frontend) preserve @ character when esc is pressed after typing it #1512
2121
- 🐛(frontend) fix pdf embed to use full width #1526
22+
- 🐛(frontend) prevent duplicate emoji when used as first character in t… #1595
23+
2224

2325
## [3.9.0] - 2025-11-10
2426

src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,22 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
122122
if (isTopRoot) {
123123
const sanitizedTitle = updateDocTitle(doc, inputText);
124124
setTitleDisplay(sanitizedTitle);
125+
return sanitizedTitle;
125126
} else {
126-
const sanitizedTitle = updateDocTitle(
127-
doc,
128-
emoji ? `${emoji} ${inputText}` : inputText,
129-
);
127+
const { emoji: pastedEmoji } = getEmojiAndTitle(inputText);
128+
const textPreservingPastedEmoji = pastedEmoji
129+
? `\u200B${inputText}`
130+
: inputText;
131+
const finalTitle = emoji
132+
? `${emoji} ${textPreservingPastedEmoji}`
133+
: textPreservingPastedEmoji;
134+
135+
const sanitizedTitle = updateDocTitle(doc, finalTitle);
130136
const { titleWithoutEmoji: sanitizedTitleWithoutEmoji } =
131137
getEmojiAndTitle(sanitizedTitle);
132138

133139
setTitleDisplay(sanitizedTitleWithoutEmoji);
140+
return sanitizedTitleWithoutEmoji;
134141
}
135142
},
136143
[updateDocTitle, doc, emoji, isTopRoot],
@@ -139,7 +146,10 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
139146
const handleKeyDown = (e: React.KeyboardEvent) => {
140147
if (e.key === 'Enter') {
141148
e.preventDefault();
142-
handleTitleSubmit(e.currentTarget.textContent || '');
149+
const nextDisplay = handleTitleSubmit(e.currentTarget.textContent || '');
150+
if (typeof nextDisplay === 'string') {
151+
e.currentTarget.textContent = nextDisplay;
152+
}
143153
}
144154
};
145155

@@ -168,9 +178,15 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
168178
suppressContentEditableWarning={true}
169179
aria-label={`${t('Document title')}`}
170180
aria-multiline={false}
171-
onBlurCapture={(event) =>
172-
handleTitleSubmit(event.target.textContent || '')
173-
}
181+
onBlurCapture={(event) => {
182+
const nextDisplay = handleTitleSubmit(
183+
event.target.textContent || '',
184+
);
185+
// Immediately reflect sanitized display in the editable field
186+
if (typeof nextDisplay === 'string') {
187+
event.target.textContent = nextDisplay;
188+
}
189+
}}
174190
$color={colorsTokens['greyscale-1000']}
175191
$padding={{ right: 'big' }}
176192
$css={css`

src/frontend/apps/impress/src/features/docs/doc-management/utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ export const getEmojiAndTitle = (title: string) => {
2626
// Use emoji-regex library for comprehensive emoji detection compatible with ES5
2727
const regex = emojiRegex();
2828

29-
// Check if the title starts with an emoji
30-
const match = title.match(regex);
29+
// Ignore leading spaces when checking for a leading emoji
30+
const leadingSpacesLength = title.match(/^\s+/)?.[0]?.length ?? 0;
31+
const trimmedStart = title.slice(leadingSpacesLength);
32+
const match = trimmedStart.match(regex);
3133

32-
if (match && title.startsWith(match[0])) {
34+
if (match && trimmedStart.startsWith(match[0])) {
3335
const emoji = match[0];
34-
const titleWithoutEmoji = title.substring(emoji.length).trim();
36+
const titleWithoutEmoji = trimmedStart.substring(emoji.length).trim();
3537
return { emoji, titleWithoutEmoji };
3638
}
3739

0 commit comments

Comments
 (0)