diff --git a/apps/www/content/docs/components/changelog.mdx b/apps/www/content/docs/components/changelog.mdx index 291de671e3..ee862f88eb 100644 --- a/apps/www/content/docs/components/changelog.mdx +++ b/apps/www/content/docs/components/changelog.mdx @@ -8,7 +8,6 @@ Since Plate UI is not a component library, a changelog is maintained here. Use the [CLI](https://platejs.org/docs/components/cli) to install the latest version of the components. - ## December 2024 #17 ### December 20 #17.3 @@ -30,6 +29,7 @@ const onColumnChange = (widths: string[]) => { }); }; ``` +- `export-toolbar-button`: add `exportToHtml` ### December 19 #17.2 diff --git a/apps/www/src/registry/default/plate-ui/export-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/export-toolbar-button.tsx index a659a8bf70..d5f70977d8 100644 --- a/apps/www/src/registry/default/plate-ui/export-toolbar-button.tsx +++ b/apps/www/src/registry/default/plate-ui/export-toolbar-button.tsx @@ -4,8 +4,105 @@ import React from 'react'; import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; +import { withProps } from '@udecode/cn'; +import { BaseAlignPlugin } from '@udecode/plate-alignment'; +import { + BaseBoldPlugin, + BaseCodePlugin, + BaseItalicPlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseUnderlinePlugin, +} from '@udecode/plate-basic-marks'; +import { BaseBlockquotePlugin } from '@udecode/plate-block-quote'; +import { + BaseCodeBlockPlugin, + BaseCodeLinePlugin, + BaseCodeSyntaxPlugin, +} from '@udecode/plate-code-block'; +import { BaseCommentsPlugin } from '@udecode/plate-comments'; +import { + BaseParagraphPlugin, + SlateLeaf, + createSlateEditor, + serializeHtml, +} from '@udecode/plate-common'; import { toDOMNode, useEditorRef } from '@udecode/plate-common/react'; +import { BaseDatePlugin } from '@udecode/plate-date'; +import { + BaseFontBackgroundColorPlugin, + BaseFontColorPlugin, + BaseFontSizePlugin, +} from '@udecode/plate-font'; +import { + BaseHeadingPlugin, + BaseTocPlugin, + HEADING_KEYS, + HEADING_LEVELS, +} from '@udecode/plate-heading'; +import { BaseHighlightPlugin } from '@udecode/plate-highlight'; +import { BaseHorizontalRulePlugin } from '@udecode/plate-horizontal-rule'; +import { BaseIndentPlugin } from '@udecode/plate-indent'; +import { BaseIndentListPlugin } from '@udecode/plate-indent-list'; +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 { + BaseAudioPlugin, + BaseFilePlugin, + BaseImagePlugin, + BaseMediaEmbedPlugin, + BaseVideoPlugin, +} from '@udecode/plate-media'; +import { BaseMentionPlugin } from '@udecode/plate-mention'; +import { + BaseTableCellHeaderPlugin, + BaseTableCellPlugin, + BaseTablePlugin, + BaseTableRowPlugin, +} from '@udecode/plate-table'; +import { BaseTogglePlugin } from '@udecode/plate-toggle'; import { ArrowDownToLineIcon } from 'lucide-react'; +import Prism from 'prismjs'; + +import { BlockquoteElementStatic } from '@/registry/default/plate-ui/blockquote-element-static'; +import { CodeBlockElementStatic } from '@/registry/default/plate-ui/code-block-element-static'; +import { CodeLeafStatic } from '@/registry/default/plate-ui/code-leaf-static'; +import { CodeLineElementStatic } from '@/registry/default/plate-ui/code-line-element-static'; +import { CodeSyntaxLeafStatic } from '@/registry/default/plate-ui/code-syntax-leaf-static'; +import { ColumnElementStatic } from '@/registry/default/plate-ui/column-element-static'; +import { ColumnGroupElementStatic } from '@/registry/default/plate-ui/column-group-element-static'; +import { CommentLeafStatic } from '@/registry/default/plate-ui/comment-leaf-static'; +import { DateElementStatic } from '@/registry/default/plate-ui/date-element-static'; +import { HeadingElementStatic } from '@/registry/default/plate-ui/heading-element-static'; +import { HighlightLeafStatic } from '@/registry/default/plate-ui/highlight-leaf-static'; +import { HrElementStatic } from '@/registry/default/plate-ui/hr-element-static'; +import { ImageElementStatic } from '@/registry/default/plate-ui/image-element-static'; +import { + FireLiComponent, + FireMarker, +} from '@/registry/default/plate-ui/indent-fire-marker'; +import { + TodoLiStatic, + TodoMarkerStatic, +} from '@/registry/default/plate-ui/indent-todo-marker-static'; +import { KbdLeafStatic } from '@/registry/default/plate-ui/kbd-leaf-static'; +import { LinkElementStatic } from '@/registry/default/plate-ui/link-element-static'; +import { MediaAudioElementStatic } from '@/registry/default/plate-ui/media-audio-element-static'; +import { MediaFileElementStatic } from '@/registry/default/plate-ui/media-file-element-static'; +import { MediaVideoElementStatic } from '@/registry/default/plate-ui/media-video-element-static'; +import { MentionElementStatic } from '@/registry/default/plate-ui/mention-element-static'; +import { ParagraphElementStatic } from '@/registry/default/plate-ui/paragraph-element-static'; +import { + TableCellElementStatic, + TableCellHeaderStaticElement, +} from '@/registry/default/plate-ui/table-cell-element-static'; +import { TableElementStatic } from '@/registry/default/plate-ui/table-element-static'; +import { TableRowElementStatic } from '@/registry/default/plate-ui/table-row-element-static'; +import { TocElementStatic } from '@/registry/default/plate-ui/toc-element-static'; +import { ToggleElementStatic } from '@/registry/default/plate-ui/toggle-element-static'; import { DropdownMenu, @@ -15,6 +112,7 @@ import { DropdownMenuTrigger, useOpenState, } from './dropdown-menu'; +import { EditorStatic } from './editor-static'; import { ToolbarButton } from './toolbar'; export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) { @@ -36,8 +134,19 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) { return canvas; }; - const downloadFile = (href: string, filename: string) => { + 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'; @@ -62,12 +171,186 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) { }); const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true }); - downloadFile(pdfBase64, 'plate.pdf'); + downloadFile({ content: pdfBase64, filename: 'plate.pdf' }); }; const exportToImage = async () => { const canvas = await getCanvas(); - downloadFile(canvas.toDataURL('image/png'), 'plate.png'); + downloadFile({ + content: canvas.toDataURL('image/png'), + filename: 'plate.png', + }); + }; + + const exportToHtml = async () => { + const components = { + [BaseAudioPlugin.key]: MediaAudioElementStatic, + [BaseBlockquotePlugin.key]: BlockquoteElementStatic, + [BaseBoldPlugin.key]: withProps(SlateLeaf, { as: 'strong' }), + [BaseCodeBlockPlugin.key]: CodeBlockElementStatic, + [BaseCodeLinePlugin.key]: CodeLineElementStatic, + [BaseCodePlugin.key]: CodeLeafStatic, + [BaseCodeSyntaxPlugin.key]: CodeSyntaxLeafStatic, + [BaseColumnItemPlugin.key]: ColumnElementStatic, + [BaseColumnPlugin.key]: ColumnGroupElementStatic, + [BaseCommentsPlugin.key]: CommentLeafStatic, + [BaseDatePlugin.key]: DateElementStatic, + [BaseFilePlugin.key]: MediaFileElementStatic, + [BaseHighlightPlugin.key]: HighlightLeafStatic, + [BaseHorizontalRulePlugin.key]: HrElementStatic, + [BaseImagePlugin.key]: ImageElementStatic, + [BaseItalicPlugin.key]: withProps(SlateLeaf, { as: 'em' }), + [BaseKbdPlugin.key]: KbdLeafStatic, + [BaseLinkPlugin.key]: LinkElementStatic, + // [BaseMediaEmbedPlugin.key]: MediaEmbedElementStatic, + [BaseMentionPlugin.key]: MentionElementStatic, + [BaseParagraphPlugin.key]: ParagraphElementStatic, + [BaseStrikethroughPlugin.key]: withProps(SlateLeaf, { as: 'del' }), + [BaseSubscriptPlugin.key]: withProps(SlateLeaf, { as: 'sub' }), + [BaseSuperscriptPlugin.key]: withProps(SlateLeaf, { as: 'sup' }), + [BaseTableCellHeaderPlugin.key]: TableCellHeaderStaticElement, + [BaseTableCellPlugin.key]: TableCellElementStatic, + [BaseTablePlugin.key]: TableElementStatic, + [BaseTableRowPlugin.key]: TableRowElementStatic, + [BaseTocPlugin.key]: TocElementStatic, + [BaseTogglePlugin.key]: ToggleElementStatic, + [BaseUnderlinePlugin.key]: withProps(SlateLeaf, { as: 'u' }), + [BaseVideoPlugin.key]: MediaVideoElementStatic, + [HEADING_KEYS.h1]: withProps(HeadingElementStatic, { variant: 'h1' }), + [HEADING_KEYS.h2]: withProps(HeadingElementStatic, { variant: 'h2' }), + [HEADING_KEYS.h3]: withProps(HeadingElementStatic, { variant: 'h3' }), + [HEADING_KEYS.h4]: withProps(HeadingElementStatic, { variant: 'h4' }), + [HEADING_KEYS.h5]: withProps(HeadingElementStatic, { variant: 'h5' }), + [HEADING_KEYS.h6]: withProps(HeadingElementStatic, { variant: 'h6' }), + }; + + const editorStatic = createSlateEditor({ + plugins: [ + BaseColumnPlugin, + BaseColumnItemPlugin, + BaseTocPlugin, + BaseVideoPlugin, + BaseAudioPlugin, + BaseParagraphPlugin, + BaseHeadingPlugin, + BaseMediaEmbedPlugin, + BaseBoldPlugin, + BaseCodePlugin, + BaseItalicPlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseUnderlinePlugin, + BaseBlockquotePlugin, + BaseDatePlugin, + BaseCodeBlockPlugin.configure({ + options: { + prism: Prism, + }, + }), + BaseIndentPlugin.extend({ + inject: { + targetPlugins: [ + BaseParagraphPlugin.key, + BaseBlockquotePlugin.key, + BaseCodeBlockPlugin.key, + ], + }, + }), + BaseIndentListPlugin.extend({ + inject: { + targetPlugins: [ + BaseParagraphPlugin.key, + ...HEADING_LEVELS, + BaseBlockquotePlugin.key, + BaseCodeBlockPlugin.key, + BaseTogglePlugin.key, + ], + }, + options: { + listStyleTypes: { + fire: { + liComponent: FireLiComponent, + markerComponent: FireMarker, + type: 'fire', + }, + todo: { + liComponent: TodoLiStatic, + markerComponent: TodoMarkerStatic, + type: 'todo', + }, + }, + }, + }), + BaseLinkPlugin, + BaseTableRowPlugin, + BaseTablePlugin, + BaseTableCellPlugin, + BaseHorizontalRulePlugin, + BaseFontColorPlugin, + BaseFontBackgroundColorPlugin, + BaseFontSizePlugin, + BaseKbdPlugin, + BaseAlignPlugin.extend({ + inject: { + targetPlugins: [ + BaseParagraphPlugin.key, + BaseMediaEmbedPlugin.key, + ...HEADING_LEVELS, + BaseImagePlugin.key, + ], + }, + }), + BaseLineHeightPlugin, + BaseHighlightPlugin, + BaseFilePlugin, + BaseImagePlugin, + BaseMentionPlugin, + BaseCommentsPlugin, + BaseTogglePlugin, + ], + value: editor.children, + }); + + const editorHtml = await serializeHtml(editorStatic, { + components, + editorComponent: EditorStatic, + props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } }, + }); + + const prismCss = ``; + const tailwindCss = ` + + + `; + + const html = ` + +
+ + + + + + + ${tailwindCss} + ${prismCss} + + + + ${editorHtml} + + `; + + downloadFile({ content: html, filename: 'plate.html', isHtml: true }); }; return ( @@ -80,6 +363,9 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {