Skip to content

🚩(frontend) version MIT only #911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/docker-hub.yml
Original file line number Diff line number Diff line change
@@ -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 }}
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
3 changes: 3 additions & 0 deletions src/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 ----
1 change: 1 addition & 0 deletions src/frontend/apps/impress/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
NEXT_PUBLIC_API_ORIGIN=
NEXT_PUBLIC_SW_DEACTIVATED=
NEXT_PUBLIC_PUBLISH_AS_MIT=true
1 change: 1 addition & 0 deletions src/frontend/apps/impress/.env.development
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
NEXT_PUBLIC_API_ORIGIN=http://localhost:8071
NEXT_PUBLIC_PUBLISH_AS_MIT=false
NEXT_PUBLIC_SW_DEACTIVATED=true
1 change: 1 addition & 0 deletions src/frontend/apps/impress/src/custom-next.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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(<DocToolBox doc={doc as any} />, {
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();
});
Original file line number Diff line number Diff line change
@@ -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: () => <span>ModalExport</span>,
}));

it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => {
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';

render(<DocToolBox doc={doc as any} />, {
wrapper: AppWrapper,
});

expect(await screen.findByText('download')).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
@@ -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: () => <span>ModalExport</span>,
}));

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(<DocToolBox doc={doc as any} />, {
wrapper: AppWrapper,
});

await waitFor(
() => {
expect(screen.queryByText('download')).not.toBeInTheDocument();
},
{
timeout: 1000,
},
);
});
Original file line number Diff line number Diff line change
@@ -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 (
<Box
@@ -222,55 +99,12 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
</>
)}

{!isSmallMobile && (
<Button
color="tertiary-text"
icon={
<Icon iconName="download" $theme="primary" $variation="800" />
}
onClick={() => {
setIsModalExportOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
/>
)}
<DropdownMenu options={options}>
<IconOptions
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
aria-label={t('Open the document options')}
/>
</DropdownMenu>
</Box>

{modalShare.isOpen && (
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
)}
{isModalExportOpen && (
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
)}
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)}
{selectHistoryModal.isOpen && (
<ModalSelectVersion
onClose={() => selectHistoryModal.close()}
<DocToolBoxLicence
doc={doc}
modalHistory={modalHistory}
modalShare={modalShare}
/>
)}
</Box>
</Box>
);
};
Loading