Skip to content

Commit

Permalink
✨(frontend) added copy-as buttons for HTML and Markdown
Browse files Browse the repository at this point in the history
Add buttons to copy editor content as HTML or Markdown. Closes suitenumerique#300
  • Loading branch information
rvveber committed Oct 16, 2024
1 parent 97d00b6 commit 6e5453f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 3 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ and this project adheres to

## [Unreleased]

## Added

- ✨(frontend) add buttons to copy document to clipboard as HTML/Markdown #300

## Changed

- ♻️(frontend) More multi theme friendly #325
- ♻️ Bootstrap frontend #257

## Fixed

🐛(frontend) invalidate queries after removing user #336
- 🐛(frontend) invalidate queries after removing user #336


## [1.5.1] - 2024-10-10
Expand Down
75 changes: 75 additions & 0 deletions src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,81 @@ test.describe('Doc Header', () => {
}),
).toBeHidden();
});

test('It checks the copy as Markdown button', async ({
page,
browserName,
}) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
);

// create page and navigate to it
await page
.getByRole('button', {
name: 'Create a new document',
})
.click();

// Add dummy content to the doc
const editor = page.locator('.ProseMirror');
const docFirstBlock = editor.locator('.bn-block-content').first();
await docFirstBlock.click();
await page.keyboard.type('# Hello World', { delay: 100 });
const docFirstBlockContent = docFirstBlock.locator('h1');
await expect(docFirstBlockContent).toHaveText('Hello World');

// Copy content to clipboard
await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Copy as Markdown' }).click();
await expect(page.getByText('Copied to clipboard')).toBeVisible();

// Test that clipboard is in Markdown format
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.trim()).toBe('# Hello World');
});

test('It checks the copy as HTML button', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
);

// create page and navigate to it
await page
.getByRole('button', {
name: 'Create a new document',
})
.click();

// Add dummy content to the doc
const editor = page.locator('.ProseMirror');
const docFirstBlock = editor.locator('.bn-block-content').first();
await docFirstBlock.click();
await page.keyboard.type('# Hello World', { delay: 100 });
const docFirstBlockContent = docFirstBlock.locator('h1');
await expect(docFirstBlockContent).toHaveText('Hello World');

// Copy content to clipboard
await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Copy as HTML' }).click();
await expect(page.getByText('Copied to clipboard')).toBeVisible();

// Test that clipboard is in HTML format
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.trim()).toBe(
`<h1 data-level="1">Hello World</h1><p></p>`,
);
});
});

test.describe('Documents Header mobile', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Button } from '@openfun/cunningham-react';
import {
Button,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';

import { Box, DropButton, IconOptions } from '@/components';
import { useAuthStore } from '@/core';
import { usePanelEditorStore } from '@/features/docs/doc-editor/';
import { useDocStore, usePanelEditorStore } from '@/features/docs/doc-editor/';
import {
Doc,
ModalRemoveDoc,
Expand All @@ -13,6 +17,7 @@ import {
import { useResponsiveStore } from '@/stores';

import { ModalVersion, Versions } from '../../doc-versioning';
import { getEditorContentFormatted } from '../utils';

import { ModalPDF } from './ModalExport';

Expand All @@ -31,6 +36,32 @@ export const DocToolBox = ({ doc, versionId }: DocToolBoxProps) => {
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
const { isSmallMobile } = useResponsiveStore();
const { authenticated } = useAuthStore();
const { docsStore } = useDocStore();
const { toast } = useToastProvider();

const copyCurrentEditorToClipboard = async (
asFormat: 'html' | 'markdown',
) => {
const editor = docsStore[doc.id]?.editor;
if (!editor) {
toast(t('Editor unavailable'), VariantType.ERROR, { duration: 3000 });
return;
}

try {
const editorContentFormatted = await getEditorContentFormatted(
editor,
asFormat,
);
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,
});
}
};

return (
<Box
Expand Down Expand Up @@ -125,6 +156,28 @@ export const DocToolBox = ({ doc, versionId }: DocToolBoxProps) => {
{t('Delete document')}
</Button>
)}
<Button
onClick={() => {
setIsDropOpen(false);
void copyCurrentEditorToClipboard('markdown');
}}
color="primary-text"
icon={<span className="material-icons">content_copy</span>}
size="small"
>
{t('Copy as {{format}}', { format: 'Markdown' })}
</Button>
<Button
onClick={() => {
setIsDropOpen(false);
void copyCurrentEditorToClipboard('html');
}}
color="primary-text"
icon={<span className="material-icons">content_copy</span>}
size="small"
>
{t('Copy as {{format}}', { format: 'HTML' })}
</Button>
</Box>
</DropButton>
</Box>
Expand Down
14 changes: 14 additions & 0 deletions src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BlockNoteEditor } from '@blocknote/core';

export function downloadFile(blob: Blob, filename: string) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
Expand Down Expand Up @@ -144,3 +146,15 @@ export const adaptBlockNoteHTML = (html: string) => {

return html;
};

export const getEditorContentFormatted = (
editor: BlockNoteEditor,
asFormat: 'html' | 'markdown',
): Promise<string> => {
switch (asFormat) {
case 'html':
return editor.blocksToHTMLLossy();
case 'markdown':
return editor.blocksToMarkdownLossy();
}
};

0 comments on commit 6e5453f

Please sign in to comment.