✨(frontend) added copy-as buttons for HTML and Markdown
Add buttons to copy editor content as HTML or Markdown. Closes #300
This commit is contained in:
@@ -9,6 +9,10 @@ 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
|
||||
@@ -17,7 +21,7 @@ and this project adheres to
|
||||
|
||||
## Fixed
|
||||
|
||||
🐛(frontend) invalidate queries after removing user #336
|
||||
- 🐛(frontend) invalidate queries after removing user #336
|
||||
|
||||
|
||||
## [1.5.1] - 2024-10-10
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
@@ -31,6 +35,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 =
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -125,6 +155,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>
|
||||
|
||||
Reference in New Issue
Block a user