From 93227466d23da135f9a0a0e799145aff0b7e47cf Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Mon, 6 Oct 2025 14:54:25 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=B1(frontend)=20manage=20export=20emoj?= =?UTF-8?q?i=20locally?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now use exported emoji from emoji-datasource-apple package instead of relying on a CDN. During a build or dev command, the emoji images are copied from node_modules to the public assets folder. They are not versionned. --- CHANGELOG.md | 2 ++ .../__tests__/app-impress/doc-export.spec.ts | 9 ++++++--- src/frontend/apps/impress/.gitignore | 2 ++ src/frontend/apps/impress/next.config.js | 9 +++++++++ src/frontend/apps/impress/package.json | 1 + .../public/assets/fonts/emoji/fallback.png | Bin 0 -> 2016 bytes .../docs/doc-export/components/ModalExport.tsx | 16 +++++++++++++++- src/frontend/yarn.lock | 14 +++++++++++++- 8 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/frontend/apps/impress/public/assets/fonts/emoji/fallback.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba02295..b644f79e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to - ♿ restyle checked checkboxes: removing strikethrough #1439 - ♿ add h1 for SR on 40X pages and remove alt texts #1438 - ♿ update labels and shared document icon accessibility #1442 +- 🍱(frontend) Fonts GDPR compliants #1453 ### Fixed @@ -38,6 +39,7 @@ and this project adheres to - 🐛(frontend) fix legacy role computation #1376 - 🛂(frontend) block editing title when not allowed #1412 - 🐛(frontend) scroll back to top when navigate to a document #1406 +- 🐛(frontend) fix export pdf emoji problem #1453 - 🐛(frontend) fix attachment download filename #1447 - 🐛(frontend) exclude h4-h6 headings from table of contents #1441 - 🔒(frontend) prevent readers from changing callout emoji #1449 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts index 5824359d..af138421 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts @@ -11,6 +11,7 @@ import { verifyDocName, waitForLanguageSwitch, } from './utils-common'; +import { openSuggestionMenu, writeInEditor } from './utils-editor'; import { createRootSubPage } from './utils-sub-pages'; test.beforeEach(async ({ page }) => { @@ -153,11 +154,13 @@ test.describe('Doc Export', () => { await verifyDocName(page, randomDoc); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World'); + await writeInEditor({ + page, + text: 'Hello World 😃🎉🚀🙋‍♀️🧑🏿‍❤️‍💋‍🧑🏾', + }); await page.keyboard.press('Enter'); - await page.locator('.bn-block-outer').last().fill('/'); + await openSuggestionMenu({ page }); await page.getByText('Resizable image with caption').click(); const fileChooserPromise = page.waitForEvent('filechooser'); diff --git a/src/frontend/apps/impress/.gitignore b/src/frontend/apps/impress/.gitignore index 9a57ee73..4501359e 100644 --- a/src/frontend/apps/impress/.gitignore +++ b/src/frontend/apps/impress/.gitignore @@ -37,4 +37,6 @@ yarn-error.log* service-worker.js # Font embedding +public/assets/fonts/emoji/* +!public/assets/fonts/emoji/fallback.png public/assets/fonts/Marianne/* \ No newline at end of file diff --git a/src/frontend/apps/impress/next.config.js b/src/frontend/apps/impress/next.config.js index 73869366..a52c38db 100644 --- a/src/frontend/apps/impress/next.config.js +++ b/src/frontend/apps/impress/next.config.js @@ -47,12 +47,21 @@ const nextConfig = { config.plugins.push( new CopyPlugin({ patterns: [ + { + from: path.resolve( + __dirname, + '../../node_modules/emoji-datasource-apple/img/apple/64', + ), + to: path.resolve(__dirname, 'public/assets/fonts/emoji'), + force: true, + }, { from: path.resolve( __dirname, '../../node_modules/@gouvfr-lasuite/ui-kit/dist/assets/fonts/Marianne', ), to: path.resolve(__dirname, 'public/assets/fonts/Marianne'), + force: true, }, ], }), diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index 42cf6961..8b1990dd 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -43,6 +43,7 @@ "cmdk": "1.1.1", "crisp-sdk-web": "1.0.25", "docx": "9.5.0", + "emoji-datasource-apple": "16.0.0", "emoji-mart": "5.6.0", "emoji-regex": "10.5.0", "i18next": "25.5.2", diff --git a/src/frontend/apps/impress/public/assets/fonts/emoji/fallback.png b/src/frontend/apps/impress/public/assets/fonts/emoji/fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..7fcaf855c9f2542f1c28dcf12c155e7231cb811a GIT binary patch literal 2016 zcmV<62Os!}P)vf3jtlij2O&ft!O4Fho zC@Ke#svb(A2ys9vv=gw%XqC=?_kSldnhB2+sMi5aKCB+=+;Qh&P;f6|V*$BBW`0%3AwIRvbdewckcy zjO}HNJwgbfKFqzAueY4wYURr1@{|zbjB{=$z*SQavBDU82NB;SguDX)6>DvnrfI)1 zreur>n&~cx_$5l|a{%Dy3kaSK;G9FDP^iRl{30Rbp)d^Rlu{=+=SLZ1|KXg!g^0_} zIh!O2i{rREj^n%HIDXU^bFa0wZrdh=3{gt&h7YO%)xL>{r=uwPbyru{p(vLd1%UF} zxKb&Ia=CW_U}|z=;w9(YF96_c4T=zy(mMg*d0(2n8bImQPfBlZ?_>}J-?P?M-d$K| zn66kXPESrw{sa;4LByeYaS0)PT5ARX#)oFF21HQ=003#4+Qr4i%0|-8&d!b(3Wd{6 z7!VRd5Ih;+bre|rh2Q9J&z?OdNs>35b9F}uN@*g5@HTt%+5&_SHgwuxgmaErZ=ZAizEbKB<#PGG8_~EHuLhLM>*hNRId%H9Dt32&Hwc1HWc7bU1f|rm zFbx0dL+5I|8W4uzhSPL*bUYkI(Ki@lnInKw>di0=e_p9n8qcaWh*twDl?}~(;M}=$ z`||nx52ch*R^_FXWv%rud;9uM`Ovr)uLkt?^)(&$$l=4o9UUD{YpsW~nm*^;MN#zo zTrT&!h57lqo1~k?s{sr1^G(G&cKmo}M?U`}t@XaFVUIBeQ53zXwSF>5l0~07uPeuA zW=Ya`g7)VZULZSfx#e3?6n&X7R zv6VByg_XX(F5XJZkQwP(%9a@rMbT(JpMS1;c(Mix05AL0(kxyLP)cQnef+lD{y`}{ zS?leu+z4sQl~u0>^zYb_8Macv)Mt4<@M=Ia9654C3(kv_QfaMSE>|j7pvK{8BMuxm z;6;zuJ3@s*Au~*H-WdeJgHp-|8DqN%A<8+on5O9~#+ZX?njQs!rq=+IQ&T=9`)vW8 z9UV2F$8p@NwSGE^qOWky3T0RQoqG+$y`r&HjYvG&&N~z+UKMDYU z0f3fngRQ3JM`KdTdpYODX3|qiZ>5xe3O=j`Fm`=*ufFyg@9*mysdwPH8bU~bh$B8V zHKL`f!9XcWDU}5MErAD_ts!&QT=lqgSO^s;jH{kR4-REXz zX8z@zdyGsMdN*7e!tT&|0s1 zHZn3YeevSOCn=@RI_Gv~ZHH^EN43^JGsbM$jxfL$oeK~`{?0l7CIEcjT6>RkE?Rl+ zIOlUg5F8D|@TUOa_4Y?IwB(}XT1uso1Ar5Yi;H6ba2Fxu4njzdF?N}Ael`e#F#yQk yQ*R+&7egOLKJx!HcGG}%3uret4QRK3cJqJh+Ktp+se;1*0000#V literal 0 HcmV?d00001 diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx index 6c8bd1af..3c3505b5 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx @@ -10,6 +10,7 @@ import { useToastProvider, } from '@openfun/cunningham-react'; import { DocumentProps, pdf } from '@react-pdf/renderer'; +import jsonemoji from 'emoji-datasource-apple' assert { type: 'json' }; import i18next from 'i18next'; import { cloneElement, isValidElement, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -95,6 +96,20 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { if (format === DocDownloadFormat.PDF) { const exporter = new PDFExporter(editor.schema, pdfDocsSchemaMappings, { resolveFileUrl: async (url) => exportCorsResolveFileUrl(doc.id, url), + emojiSource: { + format: 'png', + builder(code) { + const emoji = jsonemoji.find((e) => + e.unified.toLocaleLowerCase().includes(code.toLowerCase()), + ); + + if (emoji) { + return `/assets/fonts/emoji/${emoji.image}`; + } + + return '/assets/fonts/emoji/fallback.png'; + }, + }, }); const rawPdfDocument = (await exporter.toReactPDFDocument( exportDocument, @@ -150,7 +165,6 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { color="secondary" fullWidth onClick={() => onClose()} - disabled={isExporting} > {t('Cancel')} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 594fa679..531f3845 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -8633,7 +8633,7 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -debug@4, debug@^4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0, debug@^4.4.1: +debug@4, debug@^4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0, debug@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== @@ -8654,6 +8654,13 @@ debug@^3.1.0, debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decimal.js@^10.4.3, decimal.js@^10.5.0: version "10.6.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" @@ -8907,6 +8914,11 @@ emittery@^0.13.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== +emoji-datasource-apple@16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/emoji-datasource-apple/-/emoji-datasource-apple-16.0.0.tgz#c6e0794c1fd1b88765b880b2137e11d8efa94020" + integrity sha512-dVYjsK0FnCry9F+PBtnivhG2K0xdwlmqYaSgiUtztUdAGPYiHYhZcVKvNBqC791g2qyEcFNTBO6utg4eQ3uLTw== + emoji-mart@5.6.0, emoji-mart@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.6.0.tgz#71b3ed0091d3e8c68487b240d9d6d9a73c27f023"