Skip to content

Commit

Permalink
Merge pull request #3993 from udecode/export/md
Browse files Browse the repository at this point in the history
export/import markdown
  • Loading branch information
felixfeng33 authored Jan 16, 2025
2 parents 677bf5c + d3a147f commit f4cea12
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 38 deletions.
5 changes: 4 additions & 1 deletion apps/www/content/docs/en/components/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver

## January 2025 #18

### January 16 #18.4
- `import-toolbar-button` and `export-toolbar-button`: add `markdown` support

### January 14 #18.3
- `import-toolbar-button`: add `import-toolbar-button`
- `fixed-toolbar-buttons`: add `import-toolbar-button`
- `indent-fire-marker.tsx` Add `data-plate-prevent-deserialization` to prevent deserialization of the fire marker. Change the `span` tag to `li`.
- `indent-todo-marker.tsx` change the `span` tag to `li`.
- `image-element-static.tsx` and `hr-element-static.tsx`: Fix `nodeProps` not being passed to `SlateElement`.
Expand Down
57 changes: 29 additions & 28 deletions apps/www/src/registry/default/plate-ui/export-toolbar-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { BaseKbdPlugin } from '@udecode/plate-kbd';
import { BaseColumnItemPlugin, BaseColumnPlugin } from '@udecode/plate-layout';
import { BaseLineHeightPlugin } from '@udecode/plate-line-height';
import { BaseLinkPlugin } from '@udecode/plate-link';
import { MarkdownPlugin } from '@udecode/plate-markdown';
import {
BaseEquationPlugin,
BaseInlineEquationPlugin,
Expand Down Expand Up @@ -142,25 +143,21 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
return canvas;
};

const downloadFile = ({
content,
filename,
isHtml = false,
}: {
content: string;
filename: string;
isHtml?: boolean;
}) => {
const element = document.createElement('a');
const href = isHtml
? `data:text/html;charset=utf-8,${encodeURIComponent(content)}`
: content;
element.setAttribute('href', href);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.append(element);
element.click();
element.remove();
const downloadFile = async (url: string, filename: string) => {
const response = await fetch(url);

const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.append(link);
link.click();
link.remove();

// Clean up the blob URL
window.URL.revokeObjectURL(blobUrl);
};

const exportToPdf = async () => {
Expand All @@ -179,15 +176,12 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
});
const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });

downloadFile({ content: pdfBase64, filename: 'plate.pdf' });
await downloadFile(pdfBase64, 'plate.pdf');
};

const exportToImage = async () => {
const canvas = await getCanvas();
downloadFile({
content: canvas.toDataURL('image/png'),
filename: 'plate.png',
});
await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
};

const exportToHtml = async () => {
Expand Down Expand Up @@ -361,7 +355,15 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
</body>
</html>`;

downloadFile({ content: html, filename: 'plate.html', isHtml: true });
const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;

await downloadFile(url, 'plate.html');
};

const exportToMarkdown = async () => {
const md = editor.getApi(MarkdownPlugin).markdown.serialize();
const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
await downloadFile(url, 'plate.md');
};

return (
Expand All @@ -383,9 +385,8 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
<DropdownMenuItem onSelect={exportToImage}>
Export as Image
</DropdownMenuItem>
<DropdownMenuItem disabled>
Export as Markdown{' '}
<span className="text-xs text-muted-foreground">(coming soon)</span>
<DropdownMenuItem onSelect={exportToMarkdown}>
Export as Markdown
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
Expand Down
40 changes: 31 additions & 9 deletions apps/www/src/registry/default/plate-ui/import-toolbar-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';

import { getEditorDOMFromHtmlString } from '@udecode/plate';
import { useEditorRef } from '@udecode/plate/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
import { ArrowUpToLineIcon } from 'lucide-react';
import { useFilePicker } from 'use-file-picker';

Expand All @@ -19,22 +20,38 @@ import {
} from './dropdown-menu';
import { ToolbarButton } from './toolbar';

type ImportType = 'html' | 'markdown';

export function ImportToolbarButton({ children, ...props }: DropdownMenuProps) {
const editor = useEditorRef();
const openState = useOpenState();

const { openFilePicker } = useFilePicker({
accept: ['text/html'],
multiple: false,
onFilesSelected: async ({ plainFiles }) => {
const text = await plainFiles[0].text();
const [type, setType] = React.useState<ImportType>('html');
const accept = type === 'html' ? ['text/html'] : ['.md'];

const getFileNodes = (text: string, type: ImportType) => {
if (type === 'html') {
const editorNode = getEditorDOMFromHtmlString(text);

const nodes = editor.api.html.deserialize({
element: editorNode,
});

return nodes;
}

const nodes = editor.getApi(MarkdownPlugin).markdown.deserialize(text);

return nodes;
};

const { openFilePicker } = useFilePicker({
accept,
multiple: false,
onFilesSelected: async ({ plainFiles }) => {
const text = await plainFiles[0].text();

const nodes = getFileNodes(text, type);

editor.tf.insertNodes(nodes);
},
});
Expand All @@ -51,15 +68,20 @@ export function ImportToolbarButton({ children, ...props }: DropdownMenuProps) {
<DropdownMenuGroup>
<DropdownMenuItem
onSelect={() => {
setType('html');
openFilePicker();
}}
>
Import from HTML
</DropdownMenuItem>

<DropdownMenuItem disabled>
Import from Markdown{' '}
<span className="text-xs text-muted-foreground">(coming soon)</span>
<DropdownMenuItem
onSelect={() => {
setType('markdown');
openFilePicker();
}}
>
Import from Markdown
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
Expand Down

0 comments on commit f4cea12

Please sign in to comment.