diff --git a/src/frontend/apps/impress/.eslintrc.js b/src/frontend/apps/impress/.eslintrc.js deleted file mode 100644 index f2dbec76..00000000 --- a/src/frontend/apps/impress/.eslintrc.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - extends: ['impress/next'], - parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], - }, - settings: { - next: { - rootDir: __dirname, - }, - }, - ignorePatterns: ['node_modules', '.eslintrc.js', 'service-worker.js'], -}; diff --git a/src/frontend/apps/impress/eslint.config.mjs b/src/frontend/apps/impress/eslint.config.mjs new file mode 100644 index 00000000..ba1fe17f --- /dev/null +++ b/src/frontend/apps/impress/eslint.config.mjs @@ -0,0 +1,24 @@ +import { defineConfig } from '@eslint/config-helpers'; +import docsPlugin from 'eslint-plugin-docs'; + +const eslintConfig = defineConfig([ + { + plugins: { + docs: docsPlugin, + }, + extends: ['docs/next'], + languageOptions: { + parserOptions: { + tsconfigRootDir: import.meta.dirname, + project: ['./tsconfig.json'], + }, + }, + settings: { + next: { + rootDir: import.meta.dirname, + }, + }, + }, +]); + +export default eslintConfig; diff --git a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownKeyboardNav.ts b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownKeyboardNav.ts index 0fd41e6a..b75ae7d4 100644 --- a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownKeyboardNav.ts +++ b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownKeyboardNav.ts @@ -32,7 +32,7 @@ export const useDropdownKeyboardNav = ({ .filter((index) => index !== -1); switch (event.key) { - case 'ArrowDown': + case 'ArrowDown': { event.preventDefault(); const nextIndex = focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0; @@ -40,8 +40,9 @@ export const useDropdownKeyboardNav = ({ setFocusedIndex(nextIndex); menuItemRefs.current[nextEnabledIndex]?.focus(); break; + } - case 'ArrowUp': + case 'ArrowUp': { event.preventDefault(); const prevIndex = focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1; @@ -49,9 +50,10 @@ export const useDropdownKeyboardNav = ({ setFocusedIndex(prevIndex); menuItemRefs.current[prevEnabledIndex]?.focus(); break; + } case 'Enter': - case ' ': + case ' ': { event.preventDefault(); if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) { const selectedOptionIndex = enabledIndices[focusedIndex]; @@ -62,6 +64,7 @@ export const useDropdownKeyboardNav = ({ } } break; + } case 'Escape': event.preventDefault(); diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx index e9b5f5bc..88185a5c 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx @@ -55,7 +55,6 @@ export const QuickSearchInput = ({ )} { diff --git a/src/frontend/apps/impress/src/custom-next.d.ts b/src/frontend/apps/impress/src/custom-next.d.ts index 0e5e6acf..f6610a52 100644 --- a/src/frontend/apps/impress/src/custom-next.d.ts +++ b/src/frontend/apps/impress/src/custom-next.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - declare module '*.svg' { import * as React from 'react'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx index 45bd1ed4..08239779 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx @@ -205,7 +205,7 @@ type ItemProps = Omit & { onClick: (e: React.MouseEvent) => void; }; -interface AIMenuItemTransform { +interface AIMenuItemTransformProps { action: AITransformActions; docId: string; icon?: ReactNode; @@ -216,7 +216,7 @@ const AIMenuItemTransform = ({ action, children, icon, -}: PropsWithChildren) => { +}: PropsWithChildren) => { const { mutateAsync: requestAI, isPending } = useDocAITransform(); const editor = useBlockNoteEditor(); @@ -244,7 +244,7 @@ const AIMenuItemTransform = ({ ); }; -interface AIMenuItemTranslate { +interface AIMenuItemTranslateProps { language: string; docId: string; icon?: ReactNode; @@ -255,7 +255,7 @@ const AIMenuItemTranslate = ({ docId, icon, language, -}: PropsWithChildren) => { +}: PropsWithChildren) => { const { mutateAsync: requestAI, isPending } = useDocAITranslate(); const editor = useBlockNoteEditor(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx index 94d56da6..d49eff6a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/rules-of-hooks */ import { createReactInlineContentSpec } from '@blocknote/react'; import { TFunction } from 'i18next'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index 4de6fd4c..772bc71f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/rules-of-hooks */ import { PartialCustomInlineContentFromConfig, StyleSchema, diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx index 728d5f3a..cad4fd69 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/blocks-mapping/imagePDF.tsx @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/alt-text */ import { DefaultProps } from '@blocknote/core'; import { Image, Text, View } from '@react-pdf/renderer'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx index 15732722..c2d204b7 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/alt-text */ import { Image, Link, Text } from '@react-pdf/renderer'; import DocSelectedIcon from '../assets/doc-selected.png'; 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 b54e710d..f8b404f6 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,5 +1,3 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; import { Tooltip } from '@openfun/cunningham-react'; import React, { useCallback, useEffect, useState } from 'react'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx index f097401d..e22733a0 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx @@ -65,24 +65,20 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) { return useMutation({ mutationFn: async (variables) => { - try { - // Save the document if we can first, to ensure the latest state is duplicated - if ( - variables.canSave && - provider && - provider.document.guid === variables.docId - ) { - await updateDoc({ - id: variables.docId, - content: toBase64(Y.encodeStateAsUpdate(provider.document)), - }); - } + // Save the document if we can first, to ensure the latest state is duplicated + const canSave = + variables.canSave && + provider && + provider.document.guid === variables.docId; - return await duplicateDoc(variables); - } catch (error) { - // If save fails, throw the error to prevent duplication - throw error; + if (canSave) { + await updateDoc({ + id: variables.docId, + content: toBase64(Y.encodeStateAsUpdate(provider.document)), + }); } + + return await duplicateDoc(variables); }, onSuccess: (data, variables, context) => { void queryClient.resetQueries({ diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx index 3c8e63d7..35b3f43f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx @@ -43,7 +43,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => { docId: currentDoc.id, }, { - enabled: !!!treeContext?.root?.id, + enabled: !treeContext?.root?.id, queryKey: [KEY_DOC_TREE, { id: currentDoc.id }], }, ); diff --git a/src/frontend/apps/impress/src/features/service-worker/DocsDB.ts b/src/frontend/apps/impress/src/features/service-worker/DocsDB.ts index 36465345..eb401f37 100644 --- a/src/frontend/apps/impress/src/features/service-worker/DocsDB.ts +++ b/src/frontend/apps/impress/src/features/service-worker/DocsDB.ts @@ -4,7 +4,6 @@ import { Doc, DocsResponse } from '@/docs/doc-management'; import { RequestData, RequestSerializer } from './RequestSerializer'; -// eslint-disable-next-line import/order import pkg from '@/../package.json'; export type DBRequest = { diff --git a/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx b/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx index bd6224fd..4274f6c8 100644 --- a/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx +++ b/src/frontend/apps/impress/src/features/service-worker/__tests__/ApiPlugin.test.tsx @@ -129,7 +129,6 @@ describe('ApiPlugin', () => { const request = await apiPlugin.requestWillFetch?.(requestInit); if (withClone) { - // eslint-disable-next-line jest/no-conditional-expect expect(mockedClone).toHaveBeenCalled(); } diff --git a/src/frontend/apps/impress/src/features/service-worker/service-worker-api.ts b/src/frontend/apps/impress/src/features/service-worker/service-worker-api.ts index 947b2737..98d56894 100644 --- a/src/frontend/apps/impress/src/features/service-worker/service-worker-api.ts +++ b/src/frontend/apps/impress/src/features/service-worker/service-worker-api.ts @@ -24,6 +24,9 @@ export const isApiUrl = (href: string) => { ); }; +const isDocumentApiUrl = (url: URL) => + isApiUrl(url.href) && /.*\/documents\/([a-z0-9-]+)\/$/g.test(url.href); + /** * API routes */ @@ -45,8 +48,7 @@ registerRoute( ); registerRoute( - ({ url }) => - isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g), + ({ url }) => isDocumentApiUrl(url), new NetworkOnly({ plugins: [ new ApiPlugin({ @@ -61,8 +63,7 @@ registerRoute( ); registerRoute( - ({ url }) => - isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g), + ({ url }) => isDocumentApiUrl(url), new NetworkOnly({ plugins: [ new ApiPlugin({ @@ -90,8 +91,7 @@ registerRoute( ); registerRoute( - ({ url }) => - isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g), + ({ url }) => isDocumentApiUrl(url), new NetworkOnly({ plugins: [ new ApiPlugin({ diff --git a/src/frontend/apps/impress/src/features/service-worker/service-worker.ts b/src/frontend/apps/impress/src/features/service-worker/service-worker.ts index 38701044..87693b63 100644 --- a/src/frontend/apps/impress/src/features/service-worker/service-worker.ts +++ b/src/frontend/apps/impress/src/features/service-worker/service-worker.ts @@ -18,13 +18,11 @@ import { StrategyOptions, } from 'workbox-strategies'; -// eslint-disable-next-line import/order import { DAYS_EXP, SW_DEV_URL, SW_VERSION, getCacheNameVersion } from './conf'; import { ApiPlugin } from './plugins/ApiPlugin'; import { OfflinePlugin } from './plugins/OfflinePlugin'; import { isApiUrl } from './service-worker-api'; -// eslint-disable-next-line import/order import pkg from '@/../package.json'; declare const self: ServiceWorkerGlobalScope & { @@ -130,12 +128,14 @@ setCatchHandler(async ({ request, url, event }) => { case isApiUrl(url.href): return ApiPlugin.getApiCatchHandler(); - case request.destination === 'document': - if (url.pathname.match(/^\/docs\/([a-z0-9\-]+)\/$/g)) { + case request.destination === 'document': { + const isDocPath = /^\/docs\/([a-z0-9-]+)\/$/g.test(url.pathname); + if (isDocPath) { return precacheStrategy.handle({ event, request: FALLBACK.docs }); } return precacheStrategy.handle({ event, request: FALLBACK.offline }); + } case request.destination === 'image': return precacheStrategy.handle({ event, request: FALLBACK.images }); diff --git a/src/frontend/apps/impress/src/utils/string.ts b/src/frontend/apps/impress/src/utils/string.ts index 04a7e4f9..1daf7dc6 100644 --- a/src/frontend/apps/impress/src/utils/string.ts +++ b/src/frontend/apps/impress/src/utils/string.ts @@ -1,5 +1,5 @@ export const isValidEmail = (email: string) => { - return !!email.match( - /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/, - ); + const EMAIL_REGEX = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/; + return EMAIL_REGEX.test(email); };