Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/docx-io-new-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@platejs/docx-io': minor
---

Add DOCX import/export package:

**Import:**
- `importDocx`: Convert DOCX files to Plate nodes with comment extraction

**Export:**
- `exportToDocx`: Convert Plate content to DOCX blob
- `downloadDocx`: Download DOCX files
- `exportEditorToDocx`: Export and download in one call
- `DocxExportPlugin`: Plugin with `editor.api.docxExport` and `editor.tf.docxExport` methods
- `DOCX_EXPORT_STYLES`: Default CSS styles for Word rendering

**DOCX Static Components** (in existing static files):
- `CalloutElementDocx`
- `CodeBlockElementDocx`, `CodeLineElementDocx`, `CodeSyntaxLeafDocx`
- `ColumnElementDocx`, `ColumnGroupElementDocx`
- `EquationElementDocx`, `InlineEquationElementDocx`
- `TocElementDocx`
3 changes: 2 additions & 1 deletion apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"scripts": {
"prebuild": "yarn build:contentlayer",
"build": "yarn prebuild && yarn r && next build",
"build": "yarn prebuild && yarn r && NODE_OPTIONS='--max-old-space-size=8192' next build",
"build:contentlayer": "contentlayer2 build",
"build:registry": "NODE_ENV=production tsx --tsconfig ./scripts/tsconfig.scripts.json scripts/build-registry.mts",
"build:tw": "yarn tailwindcss -i ./src/app/globals.css -o ./public/tailwind.css --minify",
Expand Down Expand Up @@ -65,6 +65,7 @@
"@platejs/diff": "workspace:^",
"@platejs/dnd": "workspace:^",
"@platejs/docx": "workspace:^",
"@platejs/docx-io": "workspace:^",
"@platejs/emoji": "workspace:^",
"@platejs/excalidraw": "workspace:^",
"@platejs/find-replace": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/block-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
{
"path": "src/registry/ui/block-list-static.tsx",
"content": "import * as React from 'react';\n\nimport type { RenderStaticNodeWrapper, TListElement } from 'platejs';\nimport type { SlateRenderElementProps } from 'platejs/static';\n\nimport { isOrderedList } from '@platejs/list';\nimport { CheckIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nconst config: Record<\n string,\n {\n Li: React.FC<SlateRenderElementProps>;\n Marker: React.FC<SlateRenderElementProps>;\n }\n> = {\n todo: {\n Li: TodoLiStatic,\n Marker: TodoMarkerStatic,\n },\n};\n\nexport const BlockListStatic: RenderStaticNodeWrapper = (props) => {\n if (!props.element.listStyleType) return;\n\n return (props) => <List {...props} />;\n};\n\nfunction List(props: SlateRenderElementProps) {\n const { listStart, listStyleType } = props.element as TListElement;\n const { Li, Marker } = config[listStyleType] ?? {};\n const List = isOrderedList(props.element) ? 'ol' : 'ul';\n\n return (\n <List\n className=\"relative m-0 p-0\"\n style={{ listStyleType }}\n start={listStart}\n >\n {Marker && <Marker {...props} />}\n {Li ? <Li {...props} /> : <li>{props.children}</li>}\n </List>\n );\n}\n\nfunction TodoMarkerStatic(props: SlateRenderElementProps) {\n const checked = props.element.checked as boolean;\n\n return (\n <div contentEditable={false}>\n <button\n className={cn(\n 'peer -left-6 pointer-events-none absolute top-1 size-4 shrink-0 rounded-sm border border-primary bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n props.className\n )}\n data-state={checked ? 'checked' : 'unchecked'}\n type=\"button\"\n >\n <div className={cn('flex items-center justify-center text-current')}>\n {checked && <CheckIcon className=\"size-4\" />}\n </div>\n </button>\n </div>\n );\n}\n\nfunction TodoLiStatic(props: SlateRenderElementProps) {\n return (\n <li\n className={cn(\n 'list-none',\n (props.element.checked as boolean) &&\n 'text-muted-foreground line-through'\n )}\n >\n {props.children}\n </li>\n );\n}\n",
"content": "import * as React from 'react';\n\nimport type { RenderStaticNodeWrapper, TListElement } from 'platejs';\nimport type { SlateRenderElementProps } from 'platejs/static';\n\nimport { isOrderedList } from '@platejs/list';\nimport { CheckIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\nconst config: Record<\n string,\n {\n Li: React.FC<SlateRenderElementProps>;\n Marker: React.FC<SlateRenderElementProps>;\n }\n> = {\n todo: {\n Li: TodoLiStatic,\n Marker: TodoMarkerStatic,\n },\n};\n\nexport const BlockListStatic: RenderStaticNodeWrapper = (props) => {\n if (!props.element.listStyleType) return;\n\n return (props) => <List {...props} />;\n};\n\nfunction List(props: SlateRenderElementProps) {\n const { indent, listStart, listStyleType } = props.element as TListElement & {\n indent?: number;\n };\n const { Li, Marker } = config[listStyleType] ?? {};\n const List = isOrderedList(props.element) ? 'ol' : 'ul';\n\n // Apply margin-left for indent (24px per level) for DOCX export compatibility\n const marginLeft = indent ? `${indent * 24}px` : undefined;\n\n return (\n <List\n className=\"relative m-0 p-0\"\n style={{ listStyleType, marginLeft }}\n start={listStart}\n >\n {Marker && <Marker {...props} />}\n {Li ? <Li {...props} /> : <li>{props.children}</li>}\n </List>\n );\n}\n\nfunction TodoMarkerStatic(props: SlateRenderElementProps) {\n const checked = props.element.checked as boolean;\n\n return (\n <div contentEditable={false}>\n <button\n className={cn(\n 'peer -left-6 pointer-events-none absolute top-1 size-4 shrink-0 rounded-sm border border-primary bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n props.className\n )}\n data-state={checked ? 'checked' : 'unchecked'}\n type=\"button\"\n >\n <div className={cn('flex items-center justify-center text-current')}>\n {checked && <CheckIcon className=\"size-4\" />}\n </div>\n </button>\n </div>\n );\n}\n\nfunction TodoLiStatic(props: SlateRenderElementProps) {\n return (\n <li\n className={cn(\n 'list-none',\n (props.element.checked as boolean) &&\n 'text-muted-foreground line-through'\n )}\n >\n {props.children}\n </li>\n );\n}\n",
"type": "registry:ui"
}
],
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/date-node.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
{
"path": "src/registry/ui/date-node-static.tsx",
"content": "import * as React from 'react';\n\nimport type { TDateElement } from 'platejs';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { SlateElement } from 'platejs/static';\n\nexport function DateElementStatic(props: SlateElementProps<TDateElement>) {\n const { element } = props;\n\n return (\n <SlateElement className=\"inline-block\" {...props}>\n <span className=\"w-fit rounded-sm bg-muted px-1 text-muted-foreground\">\n {element.date ? (\n (() => {\n const today = new Date();\n const elementDate = new Date(element.date);\n const isToday =\n elementDate.getDate() === today.getDate() &&\n elementDate.getMonth() === today.getMonth() &&\n elementDate.getFullYear() === today.getFullYear();\n\n const isYesterday =\n new Date(today.setDate(today.getDate() - 1)).toDateString() ===\n elementDate.toDateString();\n const isTomorrow =\n new Date(today.setDate(today.getDate() + 2)).toDateString() ===\n elementDate.toDateString();\n\n if (isToday) return 'Today';\n if (isYesterday) return 'Yesterday';\n if (isTomorrow) return 'Tomorrow';\n\n return elementDate.toLocaleDateString(undefined, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n });\n })()\n ) : (\n <span>Pick a date</span>\n )}\n </span>\n {props.children}\n </SlateElement>\n );\n}\n",
"content": "import * as React from 'react';\n\nimport type { TDateElement } from 'platejs';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { SlateElement } from 'platejs/static';\n\nexport function DateElementStatic(props: SlateElementProps<TDateElement>) {\n const { element } = props;\n\n return (\n <SlateElement as=\"span\" className=\"inline-block\" {...props}>\n <span className=\"w-fit rounded-sm bg-muted px-1 text-muted-foreground\">\n {element.date ? (\n (() => {\n const today = new Date();\n const elementDate = new Date(element.date);\n const isToday =\n elementDate.getDate() === today.getDate() &&\n elementDate.getMonth() === today.getMonth() &&\n elementDate.getFullYear() === today.getFullYear();\n\n const isYesterday =\n new Date(today.setDate(today.getDate() - 1)).toDateString() ===\n elementDate.toDateString();\n const isTomorrow =\n new Date(today.setDate(today.getDate() + 2)).toDateString() ===\n elementDate.toDateString();\n\n if (isToday) return 'Today';\n if (isYesterday) return 'Yesterday';\n if (isTomorrow) return 'Tomorrow';\n\n return elementDate.toLocaleDateString(undefined, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n });\n })()\n ) : (\n <span>Pick a date</span>\n )}\n </span>\n {props.children}\n </SlateElement>\n );\n}\n",
"type": "registry:ui"
}
],
Expand Down
Loading