From f3d9591da8e9b2ae1dfed4840458ff480da4521d Mon Sep 17 00:00:00 2001 From: Olivier Laurendeau Date: Fri, 29 Aug 2025 10:25:40 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8(frontend)=20use=20title=20first?= =?UTF-8?q?=20emoji=20as=20doc=20icon=20in=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented emoji detection system, new DocIcon component. --- CHANGELOG.md | 1 + Makefile | 4 + README.md | 6 + src/frontend/apps/impress/package.json | 1 + .../doc-management/__tests__/utils.test.tsx | 263 ++++++++++++++++++ .../doc-management/components/DocIcon.tsx | 45 +++ .../components/SimpleDocItem.tsx | 25 +- .../src/features/docs/doc-management/utils.ts | 17 ++ .../doc-tree/components/DocSubPageItem.tsx | 18 +- src/frontend/yarn.lock | 34 ++- 10 files changed, 393 insertions(+), 21 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d2e4b298..2cdd95b30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to ### Added - 👷(CI) add bundle size check job #1268 +- ✨(frontend) use title first emoji as doc icon in tree ### Changed diff --git a/Makefile b/Makefile index 376a7f65e9..5f91be4cd6 100644 --- a/Makefile +++ b/Makefile @@ -406,6 +406,10 @@ run-frontend-development: ## Run the frontend in development mode cd $(PATH_FRONT_IMPRESS) && yarn dev .PHONY: run-frontend-development +frontend-test: ## Run the frontend tests + cd $(PATH_FRONT_IMPRESS) && yarn test +.PHONY: frontend-test + frontend-i18n-extract: ## Extract the frontend translation inside a json to be used for crowdin cd $(PATH_FRONT) && yarn i18n:extract .PHONY: frontend-i18n-extract diff --git a/README.md b/README.md index c772d37442..ee3b60034b 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,12 @@ To start all the services, except the frontend container, you can use the follow $ make run-backend ``` +To execute frontend tests & linting only +```shellscript +$ make frontend-test +$ make frontend-lint +``` + **Adding content** You can create a basic demo site by running this command: diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index f345e6a76f..b30194327c 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -41,6 +41,7 @@ "crisp-sdk-web": "1.0.25", "docx": "9.5.0", "emoji-mart": "5.6.0", + "emoji-regex": "^10.4.0", "i18next": "25.3.2", "i18next-browser-languagedetector": "8.2.0", "idb": "8.0.3", diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx new file mode 100644 index 0000000000..ac9eef775c --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx @@ -0,0 +1,263 @@ +import * as Y from 'yjs'; + +import { LinkReach, LinkRole, Role } from '../types'; +import { + base64ToBlocknoteXmlFragment, + base64ToYDoc, + currentDocRole, + getDocLinkReach, + getDocLinkRole, + getEmojiAndTitle, +} from '../utils'; + +// Mock Y.js +jest.mock('yjs', () => ({ + Doc: jest.fn().mockImplementation(() => ({ + getXmlFragment: jest.fn().mockReturnValue('mocked-xml-fragment'), + })), + applyUpdate: jest.fn(), +})); + +describe('doc-management utils', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('currentDocRole', () => { + it('should return OWNER when destroy ability is true', () => { + const abilities = { + destroy: true, + accesses_manage: false, + partial_update: false, + } as any; + + const result = currentDocRole(abilities); + + expect(result).toBe(Role.OWNER); + }); + + it('should return ADMIN when accesses_manage ability is true and destroy is false', () => { + const abilities = { + destroy: false, + accesses_manage: true, + partial_update: false, + } as any; + + const result = currentDocRole(abilities); + + expect(result).toBe(Role.ADMIN); + }); + + it('should return EDITOR when partial_update ability is true and higher abilities are false', () => { + const abilities = { + destroy: false, + accesses_manage: false, + partial_update: true, + } as any; + + const result = currentDocRole(abilities); + + expect(result).toBe(Role.EDITOR); + }); + + it('should return READER when no higher abilities are true', () => { + const abilities = { + destroy: false, + accesses_manage: false, + partial_update: false, + } as any; + + const result = currentDocRole(abilities); + + expect(result).toBe(Role.READER); + }); + }); + + describe('base64ToYDoc', () => { + it('should convert base64 string to Y.Doc', () => { + const base64String = 'dGVzdA=='; // "test" in base64 + const mockYDoc = { getXmlFragment: jest.fn() }; + + (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + + const result = base64ToYDoc(base64String); + + expect(Y.Doc).toHaveBeenCalled(); + expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); + expect(result).toBe(mockYDoc); + }); + + it('should handle empty base64 string', () => { + const base64String = ''; + const mockYDoc = { getXmlFragment: jest.fn() }; + + (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + + const result = base64ToYDoc(base64String); + + expect(Y.Doc).toHaveBeenCalled(); + expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); + expect(result).toBe(mockYDoc); + }); + }); + + describe('base64ToBlocknoteXmlFragment', () => { + it('should convert base64 to Blocknote XML fragment', () => { + const base64String = 'dGVzdA=='; + const mockYDoc = { + getXmlFragment: jest.fn().mockReturnValue('mocked-xml-fragment'), + }; + + (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + + const result = base64ToBlocknoteXmlFragment(base64String); + + expect(Y.Doc).toHaveBeenCalled(); + expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); + expect(mockYDoc.getXmlFragment).toHaveBeenCalledWith('document-store'); + expect(result).toBe('mocked-xml-fragment'); + }); + }); + + describe('getDocLinkReach', () => { + it('should return computed_link_reach when available', () => { + const doc = { + computed_link_reach: LinkReach.PUBLIC, + link_reach: LinkReach.RESTRICTED, + } as any; + + const result = getDocLinkReach(doc); + + expect(result).toBe(LinkReach.PUBLIC); + }); + + it('should fallback to link_reach when computed_link_reach is not available', () => { + const doc = { + link_reach: LinkReach.AUTHENTICATED, + } as any; + + const result = getDocLinkReach(doc); + + expect(result).toBe(LinkReach.AUTHENTICATED); + }); + + it('should handle undefined computed_link_reach', () => { + const doc = { + computed_link_reach: undefined, + link_reach: LinkReach.RESTRICTED, + } as any; + + const result = getDocLinkReach(doc); + + expect(result).toBe(LinkReach.RESTRICTED); + }); + }); + + describe('getDocLinkRole', () => { + it('should return computed_link_role when available', () => { + const doc = { + computed_link_role: LinkRole.EDITOR, + link_role: LinkRole.READER, + } as any; + + const result = getDocLinkRole(doc); + + expect(result).toBe(LinkRole.EDITOR); + }); + + it('should fallback to link_role when computed_link_role is not available', () => { + const doc = { + link_role: LinkRole.READER, + } as any; + + const result = getDocLinkRole(doc); + + expect(result).toBe(LinkRole.READER); + }); + + it('should handle undefined computed_link_role', () => { + const doc = { + computed_link_role: undefined, + link_role: LinkRole.EDITOR, + } as any; + + const result = getDocLinkRole(doc); + + expect(result).toBe(LinkRole.EDITOR); + }); + }); + + describe('getEmojiAndTitle', () => { + it('should extract emoji and title when emoji is present at the beginning', () => { + const title = '🚀 My Awesome Document'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBe('🚀'); + expect(result.titleWithoutEmoji).toBe('My Awesome Document'); + }); + + it('should handle complex emojis with modifiers', () => { + const title = '👨‍💻 Developer Notes'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBe('👨‍💻'); + expect(result.titleWithoutEmoji).toBe('Developer Notes'); + }); + + it('should handle emojis with skin tone modifiers', () => { + const title = '👍 Great Work!'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBe('👍'); + expect(result.titleWithoutEmoji).toBe('Great Work!'); + }); + + it('should return null emoji and full title when no emoji is present', () => { + const title = 'Document Without Emoji'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBeNull(); + expect(result.titleWithoutEmoji).toBe('Document Without Emoji'); + }); + + it('should handle empty title', () => { + const title = ''; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBeNull(); + expect(result.titleWithoutEmoji).toBe(''); + }); + + it('should handle title with only emoji', () => { + const title = '📝'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBe('📝'); + expect(result.titleWithoutEmoji).toBe(''); + }); + + it('should handle title with emoji in the middle (should not extract)', () => { + const title = 'My 📝 Document'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBeNull(); + expect(result.titleWithoutEmoji).toBe('My 📝 Document'); + }); + + it('should handle title with multiple emojis at the beginning', () => { + const title = '🚀📚 Project Documentation'; + + const result = getEmojiAndTitle(title); + + expect(result.emoji).toBe('🚀'); + expect(result.titleWithoutEmoji).toBe('📚 Project Documentation'); + }); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx new file mode 100644 index 0000000000..98274981d4 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx @@ -0,0 +1,45 @@ +import { Text } from '@/components'; + +type DocIconProps = { + emoji?: string | null; + defaultIcon: React.ReactNode; + iconSize?: 'sm' | 'lg'; + iconVariation?: + | '500' + | '400' + | 'text' + | '1000' + | '000' + | '100' + | '200' + | '300' + | '600' + | '700' + | '800' + | '900'; + iconWeight?: '400' | '500' | '600' | '700' | '800' | '900'; +}; + +export const DocIcon = ({ + emoji, + defaultIcon, + iconSize = 'sm', + iconVariation = '1000', + iconWeight = '400', +}: DocIconProps) => { + if (emoji) { + return ( + + ); + } + + return <>{defaultIcon}; +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index ff1524b651..f04a05ad4b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -4,12 +4,14 @@ import { css } from 'styled-components'; import { Box, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc, useTrans } from '@/docs/doc-management'; +import { Doc, getEmojiAndTitle, useTrans } from '@/docs/doc-management'; import { useResponsiveStore } from '@/stores'; import PinnedDocumentIcon from '../assets/pinned-document.svg'; import SimpleFileIcon from '../assets/simple-document.svg'; +import { DocIcon } from './DocIcon'; + const ItemTextCss = css` overflow: hidden; text-overflow: ellipsis; @@ -36,6 +38,9 @@ export const SimpleDocItem = ({ const { isDesktop } = useResponsiveStore(); const { untitledDocument } = useTrans(); + const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title || ''); + const displayTitle = titleWithoutEmoji || untitledDocument; + return ( ) : ( - - {doc.title || untitledDocument} + {displayTitle} {(!isDesktop || showAccesses) && ( { export const getDocLinkRole = (doc: Doc): LinkRole => { return doc.computed_link_role ?? doc.link_role; }; + +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); + console.log(match); + if (match && title.startsWith(match[0])) { + const emoji = match[0]; + const titleWithoutEmoji = title.substring(emoji.length).trim(); + return { emoji, titleWithoutEmoji }; + } + + return { emoji: null, titleWithoutEmoji: title }; +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx index 073824cf58..ad729d36a8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx @@ -9,7 +9,12 @@ import { css } from 'styled-components'; import { Box, Icon, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc, useTrans } from '@/features/docs/doc-management'; +import { + Doc, + getEmojiAndTitle, + useTrans, +} from '@/features/docs/doc-management'; +import { DocIcon } from '@/features/docs/doc-management/components/DocIcon'; import { useLeftPanelStore } from '@/features/left-panel'; import { useResponsiveStore } from '@/stores'; @@ -38,6 +43,9 @@ export const DocSubPageItem = (props: TreeViewNodeProps) => { const router = useRouter(); const { togglePanel } = useLeftPanelStore(); + const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title || ''); + const displayTitle = titleWithoutEmoji || untitledDocument; + const afterCreate = (createdDoc: Doc) => { const actualChildren = node.data.children ?? []; @@ -122,7 +130,11 @@ export const DocSubPageItem = (props: TreeViewNodeProps) => { $minHeight="24px" > - + } + iconSize="sm" + /> ) => { `} > - {doc.title || untitledDocument} + {displayTitle} {doc.nb_accesses_direct >= 1 && ( =3.1.1 <6" -react@*, react@19.1.0: +react@*: version "19.1.0" resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== @@ -14140,7 +14147,7 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript@*, typescript@5.9.2, typescript@^5.0.4: +typescript@*, typescript@^5.0.4: version "5.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== @@ -14178,6 +14185,11 @@ underscore.string@~3.3.4: sprintf-js "^1.1.1" util-deprecate "^1.0.2" +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -15168,7 +15180,7 @@ yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yjs@*, yjs@13.6.27, yjs@^13.6.27: +yjs@*, yjs@^13.6.27: version "13.6.27" resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.27.tgz#8899be929d57da05a0aa112d044a5c204393ab7b" integrity sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw== From 04d3a7bc7716876b86a10cfe804588886e914b5f Mon Sep 17 00:00:00 2001 From: Olivier Laurendeau Date: Fri, 15 Aug 2025 18:19:44 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7(frontend)=20increase=20test-e2?= =?UTF-8?q?e-other-browser=20action=20timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was failing at 20min, increase the timeout to 30 min --- .github/workflows/impress-frontend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/impress-frontend.yml b/.github/workflows/impress-frontend.yml index 9a2c3531f1..3f37c7671e 100644 --- a/.github/workflows/impress-frontend.yml +++ b/.github/workflows/impress-frontend.yml @@ -101,7 +101,7 @@ jobs: test-e2e-other-browser: runs-on: ubuntu-latest needs: test-e2e-chromium - timeout-minutes: 20 + timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v4 From e92c7a733a5d584888c2824e9fe39d0d029e4e4d Mon Sep 17 00:00:00 2001 From: Olivier Laurendeau Date: Fri, 29 Aug 2025 18:12:16 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20use=20title?= =?UTF-8?q?=20first=20emoji=20as=20doc=20icon=20in=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/app-impress/doc-header.spec.ts | 12 ++++ src/frontend/apps/impress/package.json | 2 +- .../doc-management/__tests__/utils.test.tsx | 23 ++++---- .../doc-management/components/DocIcon.tsx | 57 ++++++++----------- .../components/SimpleDocItem.tsx | 7 ++- .../src/features/docs/doc-management/utils.ts | 2 +- .../doc-tree/components/DocSubPageItem.tsx | 6 +- .../apps/impress/src/i18n/translations.json | 7 +++ src/frontend/yarn.lock | 34 ++++------- 9 files changed, 73 insertions(+), 77 deletions(-) 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 8547e1133e..2762734c0d 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 @@ -61,6 +61,18 @@ test.describe('Doc Header', () => { await verifyDocName(page, 'Hello World'); }); + test('it updates the title doc adding a leading emoji', async ({ + page, + browserName, + }) => { + await createDoc(page, 'doc-update', browserName, 1); + const docTitle = page.getByRole('textbox', { name: 'doc title input' }); + await expect(docTitle).toBeVisible(); + await docTitle.fill('👍 Hello World'); + await docTitle.blur(); + await verifyDocName(page, '👍 Hello World'); + }); + test('it deletes the doc', async ({ page, browserName }) => { const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1); diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index b30194327c..3ddf82b3da 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -41,7 +41,7 @@ "crisp-sdk-web": "1.0.25", "docx": "9.5.0", "emoji-mart": "5.6.0", - "emoji-regex": "^10.4.0", + "emoji-regex": "10.4.0", "i18next": "25.3.2", "i18next-browser-languagedetector": "8.2.0", "idb": "8.0.3", diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx index ac9eef775c..350673fb6c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx @@ -1,3 +1,4 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as Y from 'yjs'; import { LinkReach, LinkRole, Role } from '../types'; @@ -11,16 +12,16 @@ import { } from '../utils'; // Mock Y.js -jest.mock('yjs', () => ({ - Doc: jest.fn().mockImplementation(() => ({ - getXmlFragment: jest.fn().mockReturnValue('mocked-xml-fragment'), +vi.mock('yjs', () => ({ + Doc: vi.fn().mockImplementation(() => ({ + getXmlFragment: vi.fn().mockReturnValue('mocked-xml-fragment'), })), - applyUpdate: jest.fn(), + applyUpdate: vi.fn(), })); describe('doc-management utils', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('currentDocRole', () => { @@ -76,9 +77,9 @@ describe('doc-management utils', () => { describe('base64ToYDoc', () => { it('should convert base64 string to Y.Doc', () => { const base64String = 'dGVzdA=='; // "test" in base64 - const mockYDoc = { getXmlFragment: jest.fn() }; + const mockYDoc = { getXmlFragment: vi.fn() }; - (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToYDoc(base64String); @@ -89,9 +90,9 @@ describe('doc-management utils', () => { it('should handle empty base64 string', () => { const base64String = ''; - const mockYDoc = { getXmlFragment: jest.fn() }; + const mockYDoc = { getXmlFragment: vi.fn() }; - (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToYDoc(base64String); @@ -105,10 +106,10 @@ describe('doc-management utils', () => { it('should convert base64 to Blocknote XML fragment', () => { const base64String = 'dGVzdA=='; const mockYDoc = { - getXmlFragment: jest.fn().mockReturnValue('mocked-xml-fragment'), + getXmlFragment: vi.fn().mockReturnValue('mocked-xml-fragment'), }; - (Y.Doc as jest.Mock).mockReturnValue(mockYDoc); + (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToBlocknoteXmlFragment(base64String); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx index 98274981d4..0bcc3aa5a4 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocIcon.tsx @@ -1,45 +1,36 @@ -import { Text } from '@/components'; +import { useTranslation } from 'react-i18next'; -type DocIconProps = { +import { Text, TextType } from '@/components'; + +type DocIconProps = TextType & { emoji?: string | null; defaultIcon: React.ReactNode; - iconSize?: 'sm' | 'lg'; - iconVariation?: - | '500' - | '400' - | 'text' - | '1000' - | '000' - | '100' - | '200' - | '300' - | '600' - | '700' - | '800' - | '900'; - iconWeight?: '400' | '500' | '600' | '700' | '800' | '900'; }; export const DocIcon = ({ emoji, defaultIcon, - iconSize = 'sm', - iconVariation = '1000', - iconWeight = '400', + $size = 'sm', + $variation = '1000', + $weight = '400', + ...textProps }: DocIconProps) => { - if (emoji) { - return ( - - ); + const { t } = useTranslation(); + + if (!emoji) { + return <>{defaultIcon}; } - return <>{defaultIcon}; + return ( + + ); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index f04a05ad4b..672947acd9 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -38,8 +38,9 @@ export const SimpleDocItem = ({ const { isDesktop } = useResponsiveStore(); const { untitledDocument } = useTrans(); - const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title || ''); - const displayTitle = titleWithoutEmoji || untitledDocument; + const { emoji, titleWithoutEmoji: displayTitle } = getEmojiAndTitle( + doc.title || untitledDocument, + ); return ( } - iconSize="lg" + $size="25px" /> )} 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 7bb7316a9c..6a61600740 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 @@ -38,7 +38,7 @@ export const getEmojiAndTitle = (title: string) => { // Check if the title starts with an emoji const match = title.match(regex); - console.log(match); + if (match && title.startsWith(match[0])) { const emoji = match[0]; const titleWithoutEmoji = title.substring(emoji.length).trim(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx index ad729d36a8..408e110654 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx @@ -130,11 +130,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps) => { $minHeight="24px" > - } - iconSize="sm" - /> + } $size="sm" /> =3.1.1 <6" -react@*: +react@*, react@19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== @@ -14147,7 +14140,7 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript@*, typescript@^5.0.4: +typescript@*, typescript@5.9.2, typescript@^5.0.4: version "5.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== @@ -14185,11 +14178,6 @@ underscore.string@~3.3.4: sprintf-js "^1.1.1" util-deprecate "^1.0.2" -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== - undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -15180,7 +15168,7 @@ yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yjs@*, yjs@^13.6.27: +yjs@*, yjs@13.6.27, yjs@^13.6.27: version "13.6.27" resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.27.tgz#8899be929d57da05a0aa112d044a5c204393ab7b" integrity sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==