diff --git a/CHANGELOG.md b/CHANGELOG.md
index b644f79eb0..3620daf456 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to
### Added
- ✨(frontend) add pdf block to the editor #1293
+- ✨(frontend) add an EmojiPicker in the document tree and title #1381
### Changed
@@ -44,6 +45,10 @@ and this project adheres to
- 🐛(frontend) exclude h4-h6 headings from table of contents #1441
- 🔒(frontend) prevent readers from changing callout emoji #1449
+## Removed
+
+- 🔥(frontend) remove emoji buttons in doc grid #1419
+
## [3.7.0] - 2025-09-12
### Added
@@ -62,6 +67,8 @@ and this project adheres to
- ✨unify tab focus style for better visual consistency #1341
- ♿hide decorative icons, label menus, avoid accessible name… #1362
- ♻️(tilt) use helm dev-backend chart
+- 🩹(frontend) on main pages do not display leading emoji as page icon #1381
+- 🩹(frontend) handle properly emojis in interlinking #1381
### Removed
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/EmojiPicker.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/EmojiPicker.tsx
index de4a5c90a2..c9246f6589 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/EmojiPicker.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/EmojiPicker.tsx
@@ -9,16 +9,18 @@ interface EmojiPickerProps {
emojiData: EmojiMartData;
onClickOutside: () => void;
onEmojiSelect: ({ native }: { native: string }) => void;
+ withOverlay?: boolean;
}
export const EmojiPicker = ({
emojiData,
onClickOutside,
onEmojiSelect,
+ withOverlay = false,
}: EmojiPickerProps) => {
const { i18n } = useTranslation();
- return (
+ const pickerContent = (
);
+
+ if (withOverlay) {
+ return (
+ <>
+ {/* Overlay transparent pour fermer en cliquant à l'extérieur */}
+
+ {pickerContent}
+ >
+ );
+ }
+
+ return pickerContent;
};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CalloutBlock.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CalloutBlock.tsx
index b76cee1fde..208787ffbf 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CalloutBlock.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CalloutBlock.tsx
@@ -90,6 +90,7 @@ export const CalloutBlock = createReactBlockSpec(
emojiData={emojidata}
onClickOutside={onClickOutside}
onEmojiSelect={onEmojiSelect}
+ withOverlay={true}
/>
)}
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts
index 7aad893b84..3e7ac610a3 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts
@@ -3,3 +3,4 @@ export * from './CalloutBlock';
export * from './DividerBlock';
export * from './PdfBlock';
export * from './UploadLoaderBlock';
+export { default as emojidata } from './initEmojiCallout';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
index 395a77a754..a36d36b67e 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
@@ -3,10 +3,10 @@ import { createReactInlineContentSpec } from '@blocknote/react';
import { useEffect } from 'react';
import { css } from 'styled-components';
-import { StyledLink, Text } from '@/components';
+import { Icon, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import SelectedPageIcon from '@/docs/doc-editor/assets/doc-selected.svg';
-import { useDoc } from '@/docs/doc-management';
+import { getEmojiAndTitle, useDoc } from '@/docs/doc-management';
export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
{
@@ -52,6 +52,8 @@ interface LinkSelectedProps {
const LinkSelected = ({ url, title }: LinkSelectedProps) => {
const { colorsTokens } = useCunninghamTheme();
+ const { emoji, titleWithoutEmoji } = getEmojiAndTitle(title);
+
return (
{
transition: background-color 0.2s ease-in-out;
`}
>
-
-
- {title}
+ {emoji ? (
+
+ ) : (
+
+ )}
+
+ {titleWithoutEmoji}
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/index.ts
index 643b57fa45..3a390c3311 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/index.ts
@@ -1,2 +1,3 @@
export * from './DocEditor';
+export * from './EmojiPicker';
export * from './custom-blocks/';
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 42fea42895..b187f595dc 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
@@ -1,4 +1,3 @@
-import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
import { Tooltip } from '@openfun/cunningham-react';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -8,14 +7,15 @@ import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import {
Doc,
- KEY_DOC,
- KEY_LIST_DOC,
+ DocIcon,
+ getEmojiAndTitle,
useDocStore,
+ useDocTitleUpdate,
useIsCollaborativeEditable,
useTrans,
- useUpdateDoc,
} from '@/docs/doc-management';
-import { useBroadcastStore, useResponsiveStore } from '@/stores';
+import SimpleFileIcon from '@/features/docs/doc-management/assets/simple-document.svg';
+import { useResponsiveStore } from '@/stores';
interface DocTitleProps {
doc: Doc;
@@ -53,48 +53,26 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
const { isDesktop } = useResponsiveStore();
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
- const [titleDisplay, setTitleDisplay] = useState(doc.title);
- const treeContext = useTreeContext();
+ const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title ?? '');
+ const { spacingsTokens } = useCunninghamTheme();
const { untitledDocument } = useTrans();
+ const [titleDisplay, setTitleDisplay] = useState(titleWithoutEmoji);
- const { broadcast } = useBroadcastStore();
-
- const { mutate: updateDoc } = useUpdateDoc({
- listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
- onSuccess(updatedDoc) {
- // Broadcast to every user connected to the document
- broadcast(`${KEY_DOC}-${updatedDoc.id}`);
-
- if (!treeContext) {
- return;
- }
-
- if (treeContext.root?.id === updatedDoc.id) {
- treeContext?.setRoot(updatedDoc);
- } else {
- treeContext?.treeData.updateNode(updatedDoc.id, updatedDoc);
- }
- },
- });
+ const { updateDocTitle } = useDocTitleUpdate();
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) {
- setTitleDisplay('');
- }
-
- // If mutation we update
- if (sanitizedTitle !== doc.title) {
- setTitleDisplay(sanitizedTitle);
- updateDoc({ id: doc.id, title: sanitizedTitle });
- }
+ const sanitizedTitle = updateDocTitle(
+ doc,
+ emoji ? `${emoji} ${inputText}` : inputText,
+ );
+ const { titleWithoutEmoji: sanitizedTitleWithoutEmoji } =
+ getEmojiAndTitle(sanitizedTitle);
+
+ setTitleDisplay(sanitizedTitleWithoutEmoji);
},
- [doc.id, doc.title, updateDoc],
+ [doc, updateDocTitle, emoji],
);
const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -105,43 +83,82 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
};
useEffect(() => {
- setTitleDisplay(doc.title);
- }, [doc]);
+ setTitleDisplay(titleWithoutEmoji);
+ }, [doc, titleWithoutEmoji]);
return (
-
-
- handleTitleSubmit(event.target.textContent || '')
- }
- $color={colorsTokens['greyscale-1000']}
- $minHeight="40px"
- $padding={{ right: 'big' }}
- $css={css`
- &[contenteditable='true']:empty:not(:focus):before {
- content: '${untitledDocument}';
- color: grey;
- pointer-events: none;
- font-style: italic;
+
+
+
+
+ }
+ />
+
+
+
+
+
+ handleTitleSubmit(event.target.textContent || '')
}
- font-size: ${isDesktop
- ? css`var(--c--theme--font--sizes--h2)`
- : css`var(--c--theme--font--sizes--sm)`};
- font-weight: 700;
- outline: none;
- `}
- >
- {titleDisplay}
-
-
+ $color={colorsTokens['greyscale-1000']}
+ $padding={{ right: 'big' }}
+ $css={css`
+ &[contenteditable='true']:empty:not(:focus):before {
+ content: '${untitledDocument}';
+ color: grey;
+ pointer-events: none;
+ font-style: italic;
+ }
+ font-size: ${isDesktop
+ ? css`var(--c--theme--font--sizes--h2)`
+ : css`var(--c--theme--font--sizes--sm)`};
+ font-weight: 700;
+ outline: none;
+ `}
+ >
+ {titleDisplay}
+
+
+
);
};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
index 7d5eacfedc..a973f66b8d 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
@@ -20,6 +20,7 @@ import {
KEY_DOC,
KEY_LIST_DOC,
ModalRemoveDoc,
+ getEmojiAndTitle,
useCopyDocLink,
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
@@ -33,6 +34,7 @@ import {
import { useAnalytics } from '@/libs';
import { useResponsiveStore } from '@/stores';
+import { useDocTitleUpdate } from '../../doc-management/hooks/useDocTitleUpdate';
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
const ModalExport = Export?.ModalExport;
@@ -92,6 +94,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
});
}, [selectHistoryModal.isOpen, queryClient]);
+ // Emoji Management
+ const { emoji } = getEmojiAndTitle(doc.title ?? '');
+ const { updateDocEmoji } = useDocTitleUpdate();
+ const removeEmoji = () => {
+ updateDocEmoji(doc.id, doc.title ?? '', '');
+ };
+
const options: DropdownMenuOption[] = [
...(isSmallMobile
? [
@@ -127,6 +136,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
},
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
},
+ ...(emoji && doc.abilities.partial_update
+ ? [
+ {
+ label: t('Remove emoji'),
+ icon: 'emoji_emotions',
+ callback: removeEmoji,
+ },
+ ]
+ : []),
{
label: t('Version history'),
icon: 'history',
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg b/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg
index ee656f0d47..bddcff8a19 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg
@@ -1,6 +1,4 @@