diff --git a/src/frontend/apps/e2e/__tests__/app-impress/pad-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/pad-editor.spec.ts index 7ad22de4..e2bd07e5 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/pad-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/pad-editor.spec.ts @@ -77,4 +77,34 @@ test.describe('Pad Editor', () => { expect(pdfText).toContain('La directrice'); // This is the template text expect(pdfText).toContain('Hello World'); // This is the pad text }); + + test('markdown button converts from markdown to the editor syntax json', async ({ + page, + browserName, + }) => { + const randomPad = await createPad(page, 'pad-markdown', browserName, 1); + + await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible(); + + await page.locator('.ProseMirror.bn-editor').click(); + await page + .locator('.ProseMirror.bn-editor') + .fill('[test markdown](http://test-markdown.html)'); + + await expect(page.getByText('[test markdown]')).toBeVisible(); + + await page.getByText('[test markdown]').dblclick(); + await page + .getByRole('button', { + name: 'M', + }) + .click(); + + await expect(page.getByText('[test markdown]')).toBeHidden(); + await expect( + page.getByRole('link', { + name: 'test markdown', + }), + ).toHaveAttribute('href', 'http://test-markdown.html'); + }); }); diff --git a/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteEditor.tsx index 83bd7d32..c628e285 100644 --- a/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteEditor.tsx @@ -10,6 +10,8 @@ import { usePadStore } from '../stores'; import { Pad } from '../types'; import { randomColor } from '../utils'; +import { BlockNoteToolbar } from './BlockNoteToolbar'; + interface BlockNoteEditorProps { pad: Pad; } @@ -67,7 +69,9 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => { }; `} > - + + + ); }; diff --git a/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteToolbar.tsx b/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteToolbar.tsx new file mode 100644 index 00000000..ed451b94 --- /dev/null +++ b/src/frontend/apps/impress/src/features/pads/pad/components/BlockNoteToolbar.tsx @@ -0,0 +1,129 @@ +import { + BasicTextStyleButton, + BlockTypeSelect, + ColorStyleButton, + CreateLinkButton, + FormattingToolbar, + FormattingToolbarController, + ImageCaptionButton, + NestBlockButton, + ReplaceImageButton, + TextAlignButton, + ToolbarButton, + UnnestBlockButton, + useBlockNoteEditor, +} from '@blocknote/react'; +import '@blocknote/react/style.css'; +import { forEach, isArray } from 'lodash'; +import React from 'react'; + +export const BlockNoteToolbar = () => { + return ( + ( + + + + {/* Extra button to convert from markdown to json */} + + + + + + + + + + {/* Extra button to toggle code styles */} + + + + + + + + + + + + + + )} + /> + ); +}; + +type Block = { + type: string; + text: string; + content: Block[]; +}; + +function isBlock(block: Block): block is Block { + return ( + block.content && + isArray(block.content) && + block.content.length > 0 && + typeof block.type !== 'undefined' + ); +} + +const recursiveContent = (content: Block[], base: string = '') => { + let fullContent = base; + for (const innerContent of content) { + if (innerContent.type === 'text') { + fullContent += innerContent.text; + } else if (isBlock(innerContent)) { + fullContent = recursiveContent(innerContent.content, fullContent); + } + } + + return fullContent; +}; + +/** + * Custom Formatting Toolbar Button to convert markdown to json. + */ +export function MarkdownButton() { + const editor = useBlockNoteEditor(); + + const handleConvertMarkdown = () => { + const blocks = editor.getSelection()?.blocks; + + forEach(blocks, async (block) => { + if (!isBlock(block as unknown as Block)) { + return; + } + + try { + const fullContent = recursiveContent( + block.content as unknown as Block[], + ); + + const blockMarkdown = + await editor.tryParseMarkdownToBlocks(fullContent); + editor.replaceBlocks([block.id], blockMarkdown); + } catch (error) { + console.error('Error parsing Markdown:', error); + } + }); + }; + + return ( + + M + + ); +}