Skip to content

Commit 385b27d

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/editor-select
2 parents 6f2370a + ed40516 commit 385b27d

27 files changed

+1934
-1095
lines changed

apps/www/content/docs/components/changelog.mdx

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver
1313

1414
### November 26 #16.9
1515

16+
https://github.com/udecode/plate/pull/3809/files
1617
- Add `select-editor`, `tag-element`, `label`, `form`
1718
- Replace `cmdk` dependency with `@udecode/cmdk`. It's a controllable version of `cmdk`.
1819
- `command`: add variants
1920
- `editor`: add `select` variant
2021
- `popover`: add `animate` variant
2122

22-
https://github.com/udecode/plate/pull/3809/files
23+
https://github.com/udecode/plate/pull/3807/files
24+
- `toc-element`: remove `<nav>` tag using `<div>` instead to fix html2canvas issue
25+
- `editor`: remove `role="button"` to fix html2canvas issue
2326

2427
### November 21 #16.8
2528

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: Export
3+
---
4+
5+
<ComponentPreview name="playground-demo" id="basic-elements" />
6+
7+
<PackageInfo>
8+
9+
## Features
10+
11+
- Export editor content to:
12+
- Client-side export with no server dependencies
13+
14+
</PackageInfo>
15+
16+
## Usage
17+
18+
Install the [Export Toolbar Button](/docs/components/export-toolbar-button) component.
19+
20+
## Examples
21+
22+
### Plate UI
23+
24+
Refer to the preview above.
25+
26+
### Plate Plus
27+
28+
- Server-side PDF export:
29+
- High-quality PDF generation
30+
- Custom fonts and styling
31+
- Headers and footers
32+
- Page numbers
33+
- Font selectable
34+
- Advanced export options:
35+
- Paper size selection
36+
- Margin controls
37+
- Orientation settings
38+
- Compression level
39+
- Enterprise-ready features:
40+
{/* - Batch processing */}
41+
{/* - Watermarking */}
42+
- Custom templates
43+
- Password protection
44+
45+
Try it out with our server-side PDF export:
46+
47+
<ComponentPreviewPro name="export-demo" />

apps/www/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,15 @@
144144
"date-fns": "^3.6.0",
145145
"framer-motion": "^11.5.4",
146146
"fzf": "0.5.2",
147+
"html2canvas": "^1.4.1",
147148
"lodash.template": "^4.5.0",
148149
"lucide-react": "0.460.0",
149150
"match-sorter": "6.3.4",
150151
"next": "15.0.3",
151152
"next-contentlayer2": "^0.4.6",
152153
"next-themes": "^0.4.3",
153154
"nuqs": "^2.0.3",
155+
"pdf-lib": "^1.17.1",
154156
"prismjs": "^1.29.0",
155157
"react": "^18.3.1",
156158
"react-day-picker": "^8.10.1",

apps/www/public/ai-selection.png

-107 KB
Binary file not shown.

apps/www/public/r/index.json

+33-1
Original file line numberDiff line numberDiff line change
@@ -1656,6 +1656,37 @@
16561656
"registryDependencies": [],
16571657
"type": "registry:ui"
16581658
},
1659+
{
1660+
"dependencies": [
1661+
"html2canvas",
1662+
"pdf-lib"
1663+
],
1664+
"doc": {
1665+
"description": "A toolbar button to export editor content as PDF.",
1666+
"docs": [
1667+
{
1668+
"route": "/docs/export",
1669+
"title": "Export"
1670+
}
1671+
],
1672+
"examples": [
1673+
"basic-nodes-demo"
1674+
],
1675+
"label": "New",
1676+
"title": "Export Toolbar Button"
1677+
},
1678+
"files": [
1679+
{
1680+
"path": "plate-ui/export-toolbar-button.tsx",
1681+
"type": "registry:ui"
1682+
}
1683+
],
1684+
"name": "export-toolbar-button",
1685+
"registryDependencies": [
1686+
"toolbar"
1687+
],
1688+
"type": "registry:ui"
1689+
},
16591690
{
16601691
"dependencies": [
16611692
"@udecode/plate-caption"
@@ -2075,7 +2106,8 @@
20752106
"outdent-toolbar-button",
20762107
"table-dropdown-menu",
20772108
"toggle-toolbar-button",
2078-
"turn-into-dropdown-menu"
2109+
"turn-into-dropdown-menu",
2110+
"export-toolbar-button"
20792111
],
20802112
"type": "registry:ui"
20812113
},

apps/www/public/r/styles/default/editor.json

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@
77
"route": "https://pro.platejs.org/docs/components/editor"
88
}
99
],
10-
"examples": [
11-
"editor-default",
12-
"editor-disabled",
13-
"editor-full-width"
14-
]
10+
"examples": ["editor-default", "editor-disabled", "editor-full-width"]
1511
},
1612
"files": [
1713
{
@@ -24,4 +20,4 @@
2420
"name": "editor",
2521
"registryDependencies": [],
2622
"type": "registry:ui"
27-
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"dependencies": [
3+
"html2canvas",
4+
"pdf-lib"
5+
],
6+
"doc": {
7+
"description": "A toolbar button to export editor content as PDF.",
8+
"docs": [
9+
{
10+
"route": "/docs/export",
11+
"title": "Export"
12+
}
13+
],
14+
"examples": [
15+
"basic-nodes-demo"
16+
],
17+
"label": "New",
18+
"title": "Export Toolbar Button"
19+
},
20+
"files": [
21+
{
22+
"content": "'use client';\n\nimport React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { toDOMNode, useEditorRef } from '@udecode/plate-common/react';\nimport { ArrowDownToLineIcon } from 'lucide-react';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\nexport function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const getCanvas = async () => {\n const { default: html2canvas } = await import('html2canvas');\n\n const style = document.createElement('style');\n document.head.append(style);\n style.sheet?.insertRule(\n 'body > div:last-child img { display: inline-block !important; }'\n );\n\n const canvas = await html2canvas(toDOMNode(editor, editor)!);\n style.remove();\n\n return canvas;\n };\n\n const downloadFile = (href: string, filename: string) => {\n const element = document.createElement('a');\n element.setAttribute('href', href);\n element.setAttribute('download', filename);\n element.style.display = 'none';\n document.body.append(element);\n element.click();\n element.remove();\n };\n\n const exportToPdf = async () => {\n const canvas = await getCanvas();\n\n const PDFLib = await import('pdf-lib');\n const pdfDoc = await PDFLib.PDFDocument.create();\n const page = pdfDoc.addPage([canvas.width, canvas.height]);\n const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));\n const { height, width } = imageEmbed.scale(1);\n page.drawImage(imageEmbed, {\n height,\n width,\n x: 0,\n y: 0,\n });\n const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });\n\n downloadFile(pdfBase64, 'plate.pdf');\n };\n\n const exportToImage = async () => {\n const canvas = await getCanvas();\n downloadFile(canvas.toDataURL('image/png'), 'plate.png');\n };\n\n return (\n <DropdownMenu modal={false} {...openState} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={openState.open} tooltip=\"Export\" isDropdown>\n <ArrowDownToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem onSelect={exportToPdf}>\n Export as PDF\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToImage}>\n Export as Image\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n",
23+
"path": "plate-ui/export-toolbar-button.tsx",
24+
"target": "components/plate-ui/export-toolbar-button.tsx",
25+
"type": "registry:ui"
26+
}
27+
],
28+
"name": "export-toolbar-button",
29+
"registryDependencies": [
30+
"toolbar"
31+
],
32+
"type": "registry:ui"
33+
}

apps/www/public/r/styles/default/fixed-toolbar-buttons.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"files": [
1616
{
17-
"content": "'use client';\n\nimport React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n} from '@udecode/plate-font/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport { ListStyleType } from '@udecode/plate-indent-list';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n BaselineIcon,\n BoldIcon,\n Code2Icon,\n HighlighterIcon,\n ItalicIcon,\n PaintBucketIcon,\n StrikethroughIcon,\n UnderlineIcon,\n WandSparklesIcon,\n} from 'lucide-react';\n\nimport { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu';\n\nimport { AIToolbarButton } from './ai-toolbar-button';\nimport { AlignDropdownMenu } from './align-dropdown-menu';\nimport { ColorDropdownMenu } from './color-dropdown-menu';\nimport { CommentToolbarButton } from './comment-toolbar-button';\nimport { EmojiDropdownMenu } from './emoji-dropdown-menu';\nimport { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';\nimport { IndentListToolbarButton } from './indent-list-toolbar-button';\nimport { IndentTodoToolbarButton } from './indent-todo-toolbar-button';\nimport { IndentToolbarButton } from './indent-toolbar-button';\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { LineHeightDropdownMenu } from './line-height-dropdown-menu';\nimport { LinkToolbarButton } from './link-toolbar-button';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { MediaToolbarButton } from './media-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { OutdentToolbarButton } from './outdent-toolbar-button';\nimport { TableDropdownMenu } from './table-dropdown-menu';\nimport { ToggleToolbarButton } from './toggle-toolbar-button';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n <div className=\"flex w-full\">\n {!readOnly && (\n <>\n <ToolbarGroup>\n <UndoToolbarButton />\n <RedoToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <AIToolbarButton tooltip=\"AI commands\">\n <WandSparklesIcon />\n </AIToolbarButton>\n </ToolbarGroup>\n\n <ToolbarGroup>\n <InsertDropdownMenu />\n <TurnIntoDropdownMenu />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MarkToolbarButton nodeType={BoldPlugin.key} tooltip=\"Bold (⌘+B)\">\n <BoldIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={ItalicPlugin.key}\n tooltip=\"Italic (⌘+I)\"\n >\n <ItalicIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={UnderlinePlugin.key}\n tooltip=\"Underline (⌘+U)\"\n >\n <UnderlineIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={StrikethroughPlugin.key}\n tooltip=\"Strikethrough (⌘+⇧+M)\"\n >\n <StrikethroughIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton nodeType={CodePlugin.key} tooltip=\"Code (⌘+E)\">\n <Code2Icon />\n </MarkToolbarButton>\n\n <ColorDropdownMenu\n nodeType={FontColorPlugin.key}\n tooltip=\"Text color\"\n >\n <BaselineIcon />\n </ColorDropdownMenu>\n\n <ColorDropdownMenu\n nodeType={FontBackgroundColorPlugin.key}\n tooltip=\"Background color\"\n >\n <PaintBucketIcon />\n </ColorDropdownMenu>\n </ToolbarGroup>\n\n <ToolbarGroup>\n <AlignDropdownMenu />\n\n <IndentListToolbarButton nodeType={ListStyleType.Disc} />\n <IndentListToolbarButton nodeType={ListStyleType.Decimal} />\n <IndentTodoToolbarButton />\n <ToggleToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <LinkToolbarButton />\n <TableDropdownMenu />\n <EmojiDropdownMenu />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MediaToolbarButton nodeType={ImagePlugin.key} />\n <MediaToolbarButton nodeType={VideoPlugin.key} />\n <MediaToolbarButton nodeType={AudioPlugin.key} />\n <MediaToolbarButton nodeType={FilePlugin.key} />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <LineHeightDropdownMenu />\n <OutdentToolbarButton />\n <IndentToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MoreDropdownMenu />\n </ToolbarGroup>\n </>\n )}\n\n <div className=\"grow\" />\n\n <ToolbarGroup>\n <MarkToolbarButton nodeType={HighlightPlugin.key} tooltip=\"Highlight\">\n <HighlighterIcon />\n </MarkToolbarButton>\n <CommentToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <ModeDropdownMenu />\n </ToolbarGroup>\n </div>\n );\n}\n",
17+
"content": "'use client';\n\nimport React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n} from '@udecode/plate-font/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport { ListStyleType } from '@udecode/plate-indent-list';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n ArrowUpToLineIcon,\n BaselineIcon,\n BoldIcon,\n Code2Icon,\n HighlighterIcon,\n ItalicIcon,\n PaintBucketIcon,\n StrikethroughIcon,\n UnderlineIcon,\n WandSparklesIcon,\n} from 'lucide-react';\n\nimport { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu';\n\nimport { AIToolbarButton } from './ai-toolbar-button';\nimport { AlignDropdownMenu } from './align-dropdown-menu';\nimport { ColorDropdownMenu } from './color-dropdown-menu';\nimport { CommentToolbarButton } from './comment-toolbar-button';\nimport { EmojiDropdownMenu } from './emoji-dropdown-menu';\nimport { ExportToolbarButton } from './export-toolbar-button';\nimport { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';\nimport { IndentListToolbarButton } from './indent-list-toolbar-button';\nimport { IndentTodoToolbarButton } from './indent-todo-toolbar-button';\nimport { IndentToolbarButton } from './indent-toolbar-button';\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { LineHeightDropdownMenu } from './line-height-dropdown-menu';\nimport { LinkToolbarButton } from './link-toolbar-button';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { MediaToolbarButton } from './media-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { OutdentToolbarButton } from './outdent-toolbar-button';\nimport { TableDropdownMenu } from './table-dropdown-menu';\nimport { ToggleToolbarButton } from './toggle-toolbar-button';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n <div className=\"flex w-full\">\n {!readOnly && (\n <>\n <ToolbarGroup>\n <UndoToolbarButton />\n <RedoToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <AIToolbarButton tooltip=\"AI commands\">\n <WandSparklesIcon />\n </AIToolbarButton>\n </ToolbarGroup>\n\n <ToolbarGroup>\n <ExportToolbarButton>\n <ArrowUpToLineIcon />\n </ExportToolbarButton>\n </ToolbarGroup>\n\n <ToolbarGroup>\n <InsertDropdownMenu />\n <TurnIntoDropdownMenu />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MarkToolbarButton nodeType={BoldPlugin.key} tooltip=\"Bold (⌘+B)\">\n <BoldIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={ItalicPlugin.key}\n tooltip=\"Italic (⌘+I)\"\n >\n <ItalicIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={UnderlinePlugin.key}\n tooltip=\"Underline (⌘+U)\"\n >\n <UnderlineIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton\n nodeType={StrikethroughPlugin.key}\n tooltip=\"Strikethrough (⌘+⇧+M)\"\n >\n <StrikethroughIcon />\n </MarkToolbarButton>\n\n <MarkToolbarButton nodeType={CodePlugin.key} tooltip=\"Code (⌘+E)\">\n <Code2Icon />\n </MarkToolbarButton>\n\n <ColorDropdownMenu\n nodeType={FontColorPlugin.key}\n tooltip=\"Text color\"\n >\n <BaselineIcon />\n </ColorDropdownMenu>\n\n <ColorDropdownMenu\n nodeType={FontBackgroundColorPlugin.key}\n tooltip=\"Background color\"\n >\n <PaintBucketIcon />\n </ColorDropdownMenu>\n </ToolbarGroup>\n\n <ToolbarGroup>\n <AlignDropdownMenu />\n\n <IndentListToolbarButton nodeType={ListStyleType.Disc} />\n <IndentListToolbarButton nodeType={ListStyleType.Decimal} />\n <IndentTodoToolbarButton />\n <ToggleToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <LinkToolbarButton />\n <TableDropdownMenu />\n <EmojiDropdownMenu />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MediaToolbarButton nodeType={ImagePlugin.key} />\n <MediaToolbarButton nodeType={VideoPlugin.key} />\n <MediaToolbarButton nodeType={AudioPlugin.key} />\n <MediaToolbarButton nodeType={FilePlugin.key} />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <LineHeightDropdownMenu />\n <OutdentToolbarButton />\n <IndentToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <MoreDropdownMenu />\n </ToolbarGroup>\n </>\n )}\n\n <div className=\"grow\" />\n\n <ToolbarGroup>\n <MarkToolbarButton nodeType={HighlightPlugin.key} tooltip=\"Highlight\">\n <HighlighterIcon />\n </MarkToolbarButton>\n <CommentToolbarButton />\n </ToolbarGroup>\n\n <ToolbarGroup>\n <ModeDropdownMenu />\n </ToolbarGroup>\n </div>\n );\n}\n",
1818
"path": "plate-ui/fixed-toolbar-buttons.tsx",
1919
"target": "components/plate-ui/fixed-toolbar-buttons.tsx",
2020
"type": "registry:ui"
@@ -42,7 +42,8 @@
4242
"outdent-toolbar-button",
4343
"table-dropdown-menu",
4444
"toggle-toolbar-button",
45-
"turn-into-dropdown-menu"
45+
"turn-into-dropdown-menu",
46+
"export-toolbar-button"
4647
],
4748
"type": "registry:ui"
4849
}

0 commit comments

Comments
 (0)