diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index 84b23c7e..5971fcfa 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -79,7 +79,9 @@ jobs: context: . file: ./src/frontend/Dockerfile target: frontend-production - build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000 + build-args: | + DOCKER_USER=${{ env.DOCKER_USER }}:-1000 + PUBLISH_AS_MIT=false push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 92de297c..d3acb9fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to ## Added -✨ Add a custom callout block to the editor #892 +- ✨ Add a custom callout block to the editor #892 +- 🚩(frontend) version MIT only #911 ## [3.2.1] - 2025-05-06 diff --git a/docker-compose.yml b/docker-compose.yml index 01d3b06c..e6016191 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -155,8 +155,7 @@ services: target: frontend-production args: API_ORIGIN: "http://localhost:8071" - Y_PROVIDER_URL: "ws://localhost:4444" - MEDIA_URL: "http://localhost:8083" + PUBLISH_AS_MIT: "false" SW_DEACTIVATED: "true" image: impress:frontend-development ports: diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile index 9452501e..6aa3fcbe 100644 --- a/src/frontend/Dockerfile +++ b/src/frontend/Dockerfile @@ -39,6 +39,9 @@ ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN} ARG SW_DEACTIVATED ENV NEXT_PUBLIC_SW_DEACTIVATED=${SW_DEACTIVATED} +ARG PUBLISH_AS_MIT +ENV NEXT_PUBLIC_PUBLISH_AS_MIT=${PUBLISH_AS_MIT} + RUN yarn build # ---- Front-end image ---- diff --git a/src/frontend/apps/impress/.env b/src/frontend/apps/impress/.env index 3cf0e897..bcf7592f 100644 --- a/src/frontend/apps/impress/.env +++ b/src/frontend/apps/impress/.env @@ -1,2 +1,3 @@ NEXT_PUBLIC_API_ORIGIN= NEXT_PUBLIC_SW_DEACTIVATED= +NEXT_PUBLIC_PUBLISH_AS_MIT=true diff --git a/src/frontend/apps/impress/.env.development b/src/frontend/apps/impress/.env.development index f26afb11..248c7265 100644 --- a/src/frontend/apps/impress/.env.development +++ b/src/frontend/apps/impress/.env.development @@ -1,2 +1,3 @@ NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 +NEXT_PUBLIC_PUBLISH_AS_MIT=false NEXT_PUBLIC_SW_DEACTIVATED=true diff --git a/src/frontend/apps/impress/src/custom-next.d.ts b/src/frontend/apps/impress/src/custom-next.d.ts index 2f5af638..261cf96c 100644 --- a/src/frontend/apps/impress/src/custom-next.d.ts +++ b/src/frontend/apps/impress/src/custom-next.d.ts @@ -20,6 +20,7 @@ declare module '*.svg?url' { namespace NodeJS { interface ProcessEnv { NEXT_PUBLIC_API_ORIGIN?: string; + NEXT_PUBLIC_PUBLISH_AS_MIT?: string; NEXT_PUBLIC_SW_DEACTIVATED?: string; } } diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx index 99373361..35faa9a8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx @@ -42,6 +42,7 @@ const doc = { beforeEach(() => { Analytics.clearAnalytics(); + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; }); describe('DocToolBox "Copy as HTML" option', () => { @@ -51,7 +52,9 @@ describe('DocToolBox "Copy as HTML" option', () => { render(, { wrapper: AppWrapper, }); - const optionsButton = screen.getByLabelText('Open the document options'); + const optionsButton = await screen.findByLabelText( + 'Open the document options', + ); await userEvent.click(optionsButton); expect(await screen.findByText('Copy as HTML')).toBeInTheDocument(); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx new file mode 100644 index 00000000..2624889b --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx @@ -0,0 +1,28 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { AppWrapper } from '@/tests/utils'; + +import { DocToolBox } from '../components/DocToolBox'; + +const doc = { + nb_accesses: 1, + abilities: { + versions_list: true, + destroy: true, + }, +}; + +jest.mock('@/features/docs/doc-export/', () => ({ + ModalExport: () => ModalExport, +})); + +it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; + + render(, { + wrapper: AppWrapper, + }); + + expect(await screen.findByText('download')).toBeInTheDocument(); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx new file mode 100644 index 00000000..b7901f2d --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx @@ -0,0 +1,35 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { AppWrapper } from '@/tests/utils'; + +const doc = { + nb_accesses: 1, + abilities: { + versions_list: true, + destroy: true, + }, +}; + +jest.mock('@/features/docs/doc-export/', () => ({ + ModalExport: () => ModalExport, +})); + +it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true'; + + const { DocToolBox } = await import('../components/DocToolBox'); + + render(, { + wrapper: AppWrapper, + }); + + await waitFor( + () => { + expect(screen.queryByText('download')).not.toBeInTheDocument(); + }, + { + timeout: 1000, + }, + ); +}); 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 77a1fe50..3cdadfab 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 @@ -1,170 +1,47 @@ -import { - Button, - VariantType, - useModal, - useToastProvider, -} from '@openfun/cunningham-react'; +import { Button, useModal } from '@openfun/cunningham-react'; import { useQueryClient } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; -import { - Box, - DropdownMenu, - DropdownMenuOption, - Icon, - IconOptions, -} from '@/components'; +import { Box, Icon } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { useEditorStore } from '@/docs/doc-editor/'; -import { ModalExport } from '@/docs/doc-export/'; -import { - Doc, - KEY_DOC, - KEY_LIST_DOC, - ModalRemoveDoc, - useCopyDocLink, - useCreateFavoriteDoc, - useDeleteFavoriteDoc, -} from '@/docs/doc-management'; -import { DocShareModal } from '@/docs/doc-share'; -import { - KEY_LIST_DOC_VERSIONS, - ModalSelectVersion, -} from '@/docs/doc-versioning'; -import { useAnalytics } from '@/libs'; +import { Doc } from '@/docs/doc-management'; +import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning'; import { useResponsiveStore } from '@/stores'; interface DocToolBoxProps { doc: Doc; } +const DocToolBoxLicence = dynamic(() => + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false' + ? import('./DocToolBoxLicenceAGPL').then((mod) => mod.DocToolBoxLicenceAGPL) + : import('./DocToolBoxLicenceMIT').then((mod) => mod.DocToolBoxLicenceMIT), +); + export const DocToolBox = ({ doc }: DocToolBoxProps) => { const { t } = useTranslation(); const hasAccesses = doc.nb_accesses_direct > 1 && doc.abilities.accesses_view; const queryClient = useQueryClient(); - const { spacingsTokens, colorsTokens } = useCunninghamTheme(); + const { spacingsTokens } = useCunninghamTheme(); - const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); - const [isModalExportOpen, setIsModalExportOpen] = useState(false); - const selectHistoryModal = useModal(); + const modalHistory = useModal(); const modalShare = useModal(); - const { isSmallMobile, isDesktop } = useResponsiveStore(); - const { editor } = useEditorStore(); - const { toast } = useToastProvider(); - const copyDocLink = useCopyDocLink(doc.id); - const { isFeatureFlagActivated } = useAnalytics(); - const removeFavoriteDoc = useDeleteFavoriteDoc({ - listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], - }); - const makeFavoriteDoc = useCreateFavoriteDoc({ - listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], - }); - - const options: DropdownMenuOption[] = [ - ...(isSmallMobile - ? [ - { - label: t('Share'), - icon: 'group', - callback: modalShare.open, - }, - { - label: t('Export'), - icon: 'download', - callback: () => { - setIsModalExportOpen(true); - }, - }, - { - label: t('Copy link'), - icon: 'add_link', - callback: copyDocLink, - }, - ] - : []), - { - label: doc.is_favorite ? t('Unpin') : t('Pin'), - icon: 'push_pin', - callback: () => { - if (doc.is_favorite) { - removeFavoriteDoc.mutate({ id: doc.id }); - } else { - makeFavoriteDoc.mutate({ id: doc.id }); - } - }, - testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, - }, - { - label: t('Version history'), - icon: 'history', - disabled: !doc.abilities.versions_list, - callback: () => { - selectHistoryModal.open(); - }, - show: isDesktop, - }, - - { - label: t('Copy as {{format}}', { format: 'Markdown' }), - icon: 'content_copy', - callback: () => { - void copyCurrentEditorToClipboard('markdown'); - }, - }, - { - label: t('Copy as {{format}}', { format: 'HTML' }), - icon: 'content_copy', - callback: () => { - void copyCurrentEditorToClipboard('html'); - }, - show: isFeatureFlagActivated('CopyAsHTML'), - }, - { - label: t('Delete document'), - icon: 'delete', - disabled: !doc.abilities.destroy, - callback: () => { - setIsModalRemoveOpen(true); - }, - }, - ]; - - const copyCurrentEditorToClipboard = async ( - asFormat: 'html' | 'markdown', - ) => { - if (!editor) { - toast(t('Editor unavailable'), VariantType.ERROR, { duration: 3000 }); - return; - } - - try { - const editorContentFormatted = - asFormat === 'html' - ? await editor.blocksToHTMLLossy() - : await editor.blocksToMarkdownLossy(); - await navigator.clipboard.writeText(editorContentFormatted); - toast(t('Copied to clipboard'), VariantType.SUCCESS, { duration: 3000 }); - } catch (error) { - console.error(error); - toast(t('Failed to copy to clipboard'), VariantType.ERROR, { - duration: 3000, - }); - } - }; + const { isSmallMobile } = useResponsiveStore(); useEffect(() => { - if (selectHistoryModal.isOpen) { + if (modalHistory.isOpen) { return; } void queryClient.resetQueries({ queryKey: [KEY_LIST_DOC_VERSIONS], }); - }, [selectHistoryModal.isOpen, queryClient]); + }, [modalHistory.isOpen, queryClient]); return ( { )} - {!isSmallMobile && ( -