diff --git a/.claude/ruler.toml b/.claude/ruler.toml index e469624ed4..fe6e865409 100644 --- a/.claude/ruler.toml +++ b/.claude/ruler.toml @@ -28,13 +28,13 @@ enabled = true # MCP (Model Context Protocol) server configuration [mcp] -enabled = false -# merge_strategy = "merge" +enabled = true +merge_strategy = "merge" # --- MCP Server Definitions --- -[mcp_servers.plate] +[mcp_servers.registries] command = "npx" -args = ["-y", "shadcn@canary", "registry:mcp"] +args = ["-y", "shadcn@latest", "mcp"] [mcp_servers.plate.env] REGISTRY_URL = "http://localhost:3000/rd/registry.json" diff --git a/.github/workflows/ci-templates.yml b/.github/workflows/ci-templates.yml index 8cb8ed6414..f909c65e11 100644 --- a/.github/workflows/ci-templates.yml +++ b/.github/workflows/ci-templates.yml @@ -1,4 +1,4 @@ -name: Build Templates +name: CI Templates on: push: @@ -19,8 +19,8 @@ concurrency: cancel-in-progress: true jobs: - build: - name: build + ci: + name: Lint, Typecheck, Build runs-on: ubuntu-latest strategy: fail-fast: false @@ -32,31 +32,32 @@ jobs: run: working-directory: templates/${{ matrix.template }} steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v5 - - uses: pnpm/action-setup@v2.4.1 - name: Install pnpm + - uses: oven-sh/setup-bun@v2 + name: Install bun with: - version: 8.6.1 - run_install: false + bun-version: latest - - name: Setup Node.js + - name: Use Node.js uses: actions/setup-node@v4 with: node-version: 22 - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Setup pnpm cache - uses: actions/cache@v4 + - uses: actions/cache@v4 + name: ♻️ Setup bun cache with: - path: $(pnpm store path) - key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} restore-keys: | - ${{ runner.os }}-pnpm- + ${{ runner.os }}-bun- - - name: 📥 Install dependencies - run: pnpm install --no-frozen-lockfile + - name: 📥 Install + run: bun install --frozen-lockfile - - name: 🏗 Build - run: pnpm build + - name: 🔬 Run Biome & ESLint + run: bun lint + + - name: 🕵️ Typecheck & Build + run: bun run build diff --git a/.github/workflows/lint-typecheck.yml b/.github/workflows/ci.yml similarity index 99% rename from .github/workflows/lint-typecheck.yml rename to .github/workflows/ci.yml index 066e3d543e..a8ba45f4c1 100644 --- a/.github/workflows/lint-typecheck.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: uses: ./.github/actions/yarn-nm-install - name: 🔬 Lint - run: yarn check + run: yarn lint - name: ♻️ Restore packages cache uses: actions/cache@v4 diff --git a/.github/workflows/sync-templates.yml b/.github/workflows/sync-templates.yml index 8d41ba7dbc..a5af0b4c66 100644 --- a/.github/workflows/sync-templates.yml +++ b/.github/workflows/sync-templates.yml @@ -6,8 +6,8 @@ on: - main paths: - '.github/actions/**' - - '.github/workflows/sync-templates.yml' - - 'tooling/scripts/sync-templates.sh' + - '.github/workflows/push-templates.yml' + - 'tooling/scripts/push-templates.sh' - 'templates/**' jobs: @@ -71,5 +71,5 @@ jobs: - name: 🔄 Sync Templates env: API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - run: ./tooling/scripts/sync-templates.sh "templates/${{ matrix.template }}" + run: ./tooling/scripts/push-templates.sh "templates/${{ matrix.template }}" shell: bash diff --git a/.gitignore b/.gitignore index cebd53a4c3..4aa1e093a6 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,9 @@ CLAUDE.local.md # START Ruler Generated Files /.claude/skills /.codex/config.toml +/.cursor/mcp.json /.cursor/rules +/.mcp.json /.skillz /AGENTS.md /CLAUDE.md diff --git a/apps/www/public/r/components-changelog-docs.json b/apps/www/public/r/components-changelog-docs.json index 8ee201e9c4..6a0bcabdbe 100644 --- a/apps/www/public/r/components-changelog-docs.json +++ b/apps/www/public/r/components-changelog-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/components/changelog.mdx", - "content": "---\ntitle: Changelog\ndescription: Latest component updates and announcements.\ntoc: true\n---\n\nSince Plate UI is not a component library, a changelog is maintained here.\n\nUse the [CLI](https://platejs.org/docs/components/cli) to install the latest version of the components.\n\n### October 21 #26.9\n- `suggestion-kit`: Remove `BlockSuggestion`use `SuggestionLineBreak` instead to fixes styles.\n- `use-chat`: Fix AI comment hiding when finished.\n\n### October 17 #26.8\n- **Static Components**: Updated all static component imports to use new `platejs/static` path\n - `*-node-static` components: Updated imports from `@platejs/core/react` to `platejs/static`\n - `editor-static`: Updated `PlateStatic` import path\n - `export-toolbar-button`: Updated static utilities import path\n - `import-toolbar-button`: Updated static utilities import path\n - `slate-to-html/page`: Updated static rendering imports\n - `comment-kit`, `suggestion-kit`: Updated static types imports\n\n### October 6 #26.7\n- `api/ai/command/route.ts`: Fix poor AI generation quality when blockSelecting.\n\n### October 5 #26.6\n- `ai-kit`: Removed unused `api` parameter from `useHooks` destructuring\n- `block-selection-kit`: Added keyboard shortcut handler to open AI menu with `mod+j` when blocks are selected\n\n### October 4 #26.5\n- `api/ai/command/route.ts`: fix #4669\n\n## September 2025 #26\n\n### September 28 #26.5\n- `transforms`: Fixed slash command duplicate block insertion - prevents creating duplicate blocks when selecting the same block type in empty blocks\n\n### September 20 #26.4\n- **AI Suggestions**: Major improvements to AI suggestion system with better content handling and UI enhancements\n - `ai-menu`: \n - Added new `AILoadingBar` component with animated spinner and stop functionality\n - Enhanced comment acceptance UI with Accept/Reject buttons\n - Improved context-aware menu states based on selection and operation mode\n - Better keyboard navigation with ESC to stop operations\n - `ai-toolbar-button`: Streamlined implementation\n - `api/ai/command/route.ts`: \n - Added multi-tool support (generate, edit, comment) with automatic intent classification\n - Switched to Google Gemini model (`gemini-2.5-flash`)\n - Enhanced prompt templates with placeholder support\n - Added MDX tag preservation\n - `markdown-joiner-transform`: Added smart buffering for smoother streaming of markdown content\n - `use-chat`: Simplified implementation with better error handling\n - `ai-kit`: Added markdown plugin to AI configuration\n - `markdown-kit`: Added AI plugin integration\n - `link-node`: Added AI-specific click handler functionality\n - `block-context-menu`: Removed redundant AI options (now handled by ai-menu)\n - `block-discussion`: Minor styling improvements\n - `fixed-toolbar-buttons`: Simplified AI button implementation\n\n### September 7 #26.3\n- `block-context-menu`: Fixed menu position sticking when triggered multiple times in different locations\n\n### September 5 #26.2\n- `block-draggable`: Fixed block selection to work with right-click events\n\n### September 4 #26.1\n- **AI Comments**: Added AI-powered comment functionality for document review and feedback\n - `use-chat`: Enhanced chat hook with AI comment support and improved streaming capabilities\n - `ai-menu`: Updated AI menu with comment generation options and improved UI\n - `ai-toolbar-button`: Added support for AI comment actions\n - `block-context-menu`: Integrated AI comment options into block context menu\n - `fixed-toolbar-buttons`: Added AI comment button to toolbar\n - `ai-kit`: remove all prompt templates, use directly in `api/ai/command/route.ts`.\n - `api/ai/command/route.ts`: Added comment functionality.\n\n## August 2025 #25\n\n\n### August 17 #25.2\n- `block-discussion`: Removed `useFocusedLast` check for showing discussion popover.\n\n### August 1 #25.1\n- **Floating toolbar improvements**: Multiple components now use the new `useFocusedLast` hook to only show their floating toolbars when their editor is the last focused editor, preventing toolbar conflicts in multi-editor scenarios:\n - `ai-menu`\n - `block-discussion`\n - `column-node`\n - `media-toolbar`\n - `table-node`\n- `block-draggable`: \n - Select list children on handle click\n - Focus selected blocks on handle click\n\n## July 2025 #24\n\n### July 29 #24.11\n- `block-draggable`: Fixed table drag and drop preview display with horizontal scroll compensation. Drag preview elements now correctly display content even when the original element has horizontal scroll\n- `block-draggable`: Added `isAboutToDrag` state to improve preview handling - tracks when drag is about to start (mousedown but not yet dragging) for better preview cleanup\n\n### July 27 #24.10\n- `ai-kit`: support custom node type\n- `indent-kit`: add `KEYS.img` to `IndentPlugin`\n- `list-kit`: add `KEYS.img` to `ListPlugin`\n- `markdown-joiner-transform.ts`: add `markdownJoinerTransform` to transform chunks like [**,bold,**] to [**bold**] make the md deserializer happy.\n- `api/ai/command/route.ts`: use `markdownJoinerTransform` to transform chunks.\n\n### July 26 #24.9\n- `list-classic-kit`: Added `ListPlugin` to restore List functionalities (indent with Tab / Shift+Tab, new item when pressing enter, ...).\n\n### July 25 #24.8\n- `block-draggable`: Added support for automatically selecting list item children when dragging. When dragging a list item, all nested items with bigger indent are now included in the drag operation\n\n### July 23 #24.7\n- `block-draggable`: Updated to use new `addOnContextMenu` API from BlockSelectionPlugin for cleaner context menu handling\n\n### July 18 #24.6\n- `block-context-menu`: Fixed context menu not opening when right-clicking on block margin areas\n- `block-draggable`: Added wrapper div with context menu handler to ensure block selection on margin clicks\n\n### July 14 #24.5\n- `block-draggable`: Added support for dragging multiple blocks using editor's native selection (previously only block-selection was supported)\n\n### July 3 #24.4\n- `slate-to-html`: Added `EditorViewDemo` component for static editor rendering using `createStaticEditor`\n### July 4 #24.3\n\n- `list-classic-node`: Fix styling that affects `TaskListElement` by force overriding list-style-type (set to none).\n\n### July 3 #24.2\n\n- **Task list support in list-classic**: Added task list functionality with checkboxes to the list-classic plugin\n - `list-classic-kit`: Added `TaskListPlugin` with `TaskListElement` component\n - `list-classic-node`: Added `TaskListElement` and `TaskListItemElement` components with checkbox support\n - `transforms-classic`: New file for classic list transforms\n - `insert-toolbar-classic-button`: New component for inserting classic list types (bulleted, numbered, task)\n - `turn-into-toolbar-classic-button`: New component for converting blocks to classic list types\n - `floating-toolbar-classic-buttons`: New component for floating toolbar with classic list support\n - `floating-toolbar-classic-kit`: New kit that includes classic list toolbar buttons\n\n### July 2 #24.1\n- `editor`: Added `EditorView` component using the new `PlateView` from `@platejs/core/react` for static editor rendering with copy functionality\n\n## June 2025 #23\n\n### June 29 #23.9\n- `link-node`: Remove `useLink`\n- `link-node-static`: missing `getLinkAttributes`\n- `media-image-node`: `attributes.alt` type casting\n\n### June 26 #23.7\n- `inline-combobox`: Fixed combobox not closing when clicking outside the editor\n\n### June 24 #23.6\n- `transform.ts`: add `toggleCodeBlock` to `setBlockMap`. Fix the structural error of the code_block created by `turn-into-toolbar-button.tsx`.\n\n### June 20 #23.5\n- [Drag and drop improvements](https://github.com/udecode/plate/pull/4385)\n- `block-draggable`: Fixed drag and drop functionality with multiple selected blocks and resolved drop positioning issues on margins.\n- `block-selection-kit`: It is now possible to select the entire table (table), but the rows (tr) will only be selected if your selection box is within the table.\n- `table-node`: Add block selection styles to the table.\n\n### June 18 #23.4\n\n- `table-node`: Fix bug affecting cursor position and improve performance\n\n### June 16 #23.3\n\n- `block-draggable`: use `getPluginByType` instead of `getContainerTypes`\n\n### June 13 #23.2\n\n- `editor`: Fix placeholder positioning `**:data-slate-placeholder:!top-1/2 **:data-slate-placeholder:-translate-y-1/2`.\n- `block-placeholder-kit`: Change placeholder color to `text-muted-foreground/80` to match `editor` one.\n\n### June 9 #23.1\n\n**Plate 49**\n\nMerging files, using a more consistent naming convention, and removing unused `export` statements.\n\nComponents:\n\n- Now that basic nodes have a default HTML element, you can remove `withProps(..., { as: '...' })` plugin components.\n- To improve decoupling, plugins are not imported anymore only for their keys. Import `KEYS` from `@udecode/plate` instead, as a unified source of keys.\n - `ParagraphPlugin.key` -> `KEYS.p`\n - `INDENT_LIST_KEYS.listStyleType` -> `KEYS.listType`\n - `ListStyleType.Decimal` -> `KEYS.ol`\n - `ListStyleType.Disc` -> `KEYS.ul`\n - `list` (classic) -> `KEYS.listClassic`\n - `ol` (classic) -> `KEYS.olClassic`\n - `ul` (classic) -> `KEYS.ulClassic`\n - `li` (classic) -> `KEYS.liClassic`\n - `action_item` (classic) -> `KEYS.listTodoClassic`\n- Rename all `*-element`, `*-leaf` files to `*-node` (and static versions)\n- Removed `ai-anchor-element`, merged into `ai-node`\n- Removed `ai-loading-bar`, merged into `ai-menu`\n- Removed `ai-menu-items`, merged into `ai-menu`\n- Renamed `align-dropdown-menu` to `align-toolbar-button`, `AlignDropdownMenu` to `AlignToolbarButton`\n- Renamed `api-ai` to `ai-api`\n- Renamed `api-uploadthing` to `media-uploadthing-api`\n- `BlockSelection`: fix block selection for tables\n- Removed `code-block-combobox`, merged into `code-block-node`\n- Removed `code-line-element`, merged into `code-block-node` (and static version)\n- Removed `code-syntax-leaf`, merged into `code-block-node` (and static version)\n- Rename `color-toolbar-button` to `font-color-toolbar-button`, `ColorDropdownMenu` to `FontColorToolbarButton`\n- Removed all `color-*` files, merged into `font-color-toolbar-button`\n - Rename `color-dropdown-menu` to `font-color-toolbar-button`\n- Removed `column-group-element`, merged into `column-node` (and static version)\n- Removed `comment-create-form`, merged into `comment`\n- Renamed `draggable` to `block-draggable`, `DraggableAboveNodes` to `BlockDraggable`\n- Renamed `emoji-input-element` to `emoji-node`\n- Removed all `emoji-*` files (except `emoji-input-node`), merged into `emoji-toolbar-button`\n - Rename `EmojiToolbarDropdown` to `EmojiPopover`, `EmojiDropdownMenu` to `EmojiToolbarButton`\n - `EmojiPicker` `icons` prop is now optional and defaulted to `emojiCategoryIcons` and `emojiSearchIcons`\n- Renamed `image-preview` to `media-preview-dialog`, `ImagePreview` to `MediaPreviewDialog`\n- Renamed `image-element` to `media-image-node`\n - Renamed `MediaFileElement` to `FileElement` (and static version)\n - Renamed `MediaAudioElement` to `AudioElement` (and static version)\n - Renamed `MediaVideoElement` to `VideoElement` (and static version)\n- Renamed `indent-list-toolbar-button` to `list-toolbar-button`\n - Renamed `BulletedIndentListToolbarButton` to `BulletedListToolbarButton`\n - Renamed `NumberedIndentListToolbarButton` to `NumberedListToolbarButton`\n- Renamed `indent-todo-marker` to `block-list`\n- Removed `indent-fire-marker`\n- Removed `indent-todo-toolbar-button`, merged into `list-toolbar-button`\n- Renamed `IndentTodoToolbarButton` into `TodoListToolbarButton`\n- Removed `inline-equation-element` and `equation-popover`, merged into `equation-node` (and static version)\n- Removed `inline-equation-toolbar-button`, merged into `equation-toolbar-button`\n- Renamed `insert-dropdown-menu` to `insert-toolbar-button`, `InsertDropdownMenu` to `InsertToolbarButton`\n- Renamed `line-height-dropdown-menu` to `line-height-toolbar-button`, `LineHeightDropdownMenu` to `LineHeightToolbarButton`\n- Rename `link-floating-toolbar` to `link-toolbar`\n- Removed `list-indent-toolbar-button`, merged into `list-classic-toolbar-button`\n- Renamed `ListIndentToolbarButton` to `IndentToolbarButton`\n- Renamed `list-node` to `list-classic-node`\n- Renamed `media-popover` to `media-toolbar`, `MediaPopover` to `MediaToolbar`\n- Renamed `mode-dropdown-menu` to `mode-toolbar-button`, `ModeDropdownMenu` to `ModeToolbarButton`\n- Renamed `more-dropdown-menu` to `more-toolbar-button`, `MoreDropdownMenu` to `MoreToolbarButton`\n- Removed `outdent-toolbar-button`, merged into `indent-toolbar-button`\n- `table-icons`: rename `Border*` to `Border*Icon`\n- Renamed `slash-input-element` to `slash-input-node`\n- Renamed `SuggestionBelowNodes` to `SuggestionLineBreak`\n- Removed `table-cell-element`, merged into `table-node` (and static version)\n- Removed `table-row-element`, merged into `table-node` (and static version)\n- Renamed `table-dropdown-menu` to `table-toolbar-button`, `TableDropdownMenu` to `TableToolbarButton`\n- Removed `todo-list-node`, merged into `list-classic-node`\n- Renamed `turn-into-dropdown-menu` to `turn-into-toolbar-button`, `TurnIntoDropdownMenu` to `TurnIntoToolbarButton`\n- `export-toolbar-button`, `indent-list-plugins`: remove fire from `listStyleTypes`\n- `useCommentEditor`: `usePlateEditor` instead of `useCreateEditor`\n- Removed `placeholder`, `withPlaceholder`. Migration: use `block-placeholder-kit`, `BlockPlaceholderPlugin` instead.\n- `line-height-toolbar-button`: remove `useLineHeightDropdownMenu` hook.\n- `font-color-toolbar-button`: remove `useColorInput` hook.\n\nPlugins:\n\n- Renamed all `*-plugin`, `*-plugins` files to `-kit`, and `*Plugin`, `*Plugins` to `*Kit`. A **plugin kit** is a collection of configured plugins.\n - Renamed `editor-plugins` to `editor-kit`\n - Renamed `equation-plugins` to `math-kit`, `equationPlugins` to `MathKit`\n - Renamed `indent-list-plugins` to `list-kit`, `indentListPlugins` to `ListKit`\n - Added `BlockList` component to `block-list`, used in `list-kit`\n - Removed `use-create-editor`, use `usePlateEditor` instead\n- `ai-kit`: add `show` shortcut. Remove `useHotkeys('mod+j')` from `ai-menu`\n- `comment-kit`: add `setDraft` transform, shortcut\n- `basic-marks-kit`, `basic-blocks-kit`: add shortcuts\n\n- `transforms`, `block-draggable`: remove `STRUCTURE_TYPES`, those are now inferred from `plugin.node.isContainer`. Use instead `editor.meta.containerTypes`.\n- Remove `structuralTypes` from `useSelectionFragmentProp` usages.\n\n## May 2025 #22\n\n### May 26 #22.7\n\n- [Fix combobox closing issue](https://github.com/udecode/plate/pull/4322)\n- `inline-combobox`: fix `insertPoint` not being updated when the combobox is closed.\n\n### May 15 #22.6\n\n- [Fix inline math keyboard behavior and style](https://github.com/udecode/plate/pull/4305)\n- `equation-popover`: Focus back to the editor when the popover is closed.\n- `inline-equation-element`: When selecting it should be highlighted.\n\n### May 11-12 #22.5\n\n- [Templates migration to Plate 48](https://github.com/udecode/plate/pull/4298/files)\n- Migration to shadcn v4: \n - Plate had a forked version of shadcn/ui primitives that could conflict with your existing components. Our components now **fully depend** on the original shadcn/ui primitives, easing the integration of Plate into your existing shadcn/ui set of components.\n - All components updated to [Tailwind v4](https://ui.shadcn.com/docs/tailwind-v4). \n - See the updated [installation guide](/docs/components/installation).\n- Migration to React 19. If you're using React 18, you may need to use `React.forwardRef` in a few places.\n- Migration to [shadcn CLI](https://ui.shadcn.com/docs/cli):\n - Remove `registries` from `components.json`\n - Use `npx shadcn` instead of `npx shadcx`\n- [MCP support](/docs/mcp)\n- Remove `withRef` from all components\n- Import `cn` from `@/lib/utils` instead of `@udecode/cn` to stay consistent with shadcn/ui\n- Remove unused `className`, `style` props from all element and leaf components\n- `draggable`:\n - Fix dnd in Firefox\n- `media-placeholder-element`: refactor to use `use-upload-file` hook instead of `uploadthing`\n - Migration: `npx shadcn@latest add https://platejs.org/r/api-uploadthing`\n\n### May 6 #22.3\n\n- `ai-chat-editor`: support none-standard markdown nodes.\n- `slash-input-element`: add callout support.\n- `block-selection-plugins.tsx`: fix block selection not working.\n\n### May 4 #22.2\n\n- `ai/command`: forked smoothStream from `ai` package now uses 30ms delay by default (only code blocks and tables use 100ms delay).\n\nv48 migration:\n- `PlateElement`, `PlateLeaf` and `PlateText` HTML attributes are moved from top-level props to `attributes` prop.\n- Remove `nodeProps` prop from `PlateElement`, `PlateLeaf`, `PlateText`. Use `attributes` prop instead.\n- Migrated components: \n - `block-discussion`\n - `comment-leaf`\n - `date-element`\n - `draggable`\n - `excalidraw-element`\n - `hr-element` + `-static`\n - `image-element` + `-static`\n - `link-element`\n - `media-audio-element`\n - `media-file-element`\n - `media-placeholder-element`\n - `media-video-element`\n - `mention-element`\n - `placeholder`\n - `suggestion-leaf`\n - `table-cell-element` + `-static`\n - `table-element`\n - `tag-element`\n\n### May 2 #22.1\n- `use-chat`: add `_abortFakeStream`.\n- `ai-menu`: Fix menu items are displayed incorrectly.\n- `ai-loading-bar`: Move esc keydown handler to `ai-menu`.\n- `ai/command`: add chunking delay to 100ms (Temporary workaround with performance issue).\n\n\n## April 2025 #21\n\n### April 30 #21.3\n\n- `autoformat-plugin`: allow starting a new indent list with numbers other than 1\n\n### April 29 #21.2\n\n- `ai-leaf`: add `aiIndicatorVariants` to display loading state.\n- `cursor-overlay`: hide when ai is streaming.\n- `api/ai/command`: fix chunking issue.\n\nAdd `discussion-plugin`:\n- add `discussionPlugin` to `editor-plugins`, remove `configure` from `suggestionPlugin`\n- refactor `block-suggestion`, `comment` to use `discussionPlugin`\n- fix `comment-create-form` to create discussion when none exists\n- style changes in `suggestion-leaf`\n- fix `link-floating-toolbar` to support internal links, and placement top when suggestion or comment is active\n\n### April 19 #21.1\n\n- `ai-anchor-element`: add `ai-anchor-element` component that is inserted before streaming, removed after streaming, and used for positioning the ai-menu\n- `ai-loading-bar`: add `ai-loading-bar` component that is used to display the loading progress of the insert mode streaming\n- `ai-menu`: migrate to latest `ai` package\n- `ai-menu-items`: add `generateMarkdownSample`\n- `ai-plugins`: Remove the single-paragraph limit from prompts\n- `editor`: introduce `PlateContainer` component\n\n### April 2 #21.1\n\n- `export-toolbar-button`: fix pdf export issue with `html2canvas-pro`\n- `import-toolbar-button`: fix sometimes unable to select the file\n\n## March 2025 #20\n\n### March 12 2025 #20.4\n\n- `ai-toolbar-button`: add missing `@udecode/plate-ai` dependency.\n- `comment-toolbar-button`: add missing `comments-plugin` registry dependency.\n- `font-size-toolbar-button`: add missing `popover` registry dependency.\n- `tooltip`: add missing `button` registry dependency.\n\n### March 10 #20.3\n- `block-context-menu`: Prevent opening context menu in read-only mode\n\n### March 2 #20.2\n\n- `block-suggestion`: fix styles\n- `suggestion-leaf-static`: add static versions\n\n### March 1 #20.1\n\nPlate 46 - new code block\n\n- Migrated from Prism.js to lowlight for syntax highlighting\n - `code-block-element-static`, `code-block-element`, `code-block-combobox`: Updated to use lowlight classes. Default to github theme.\n - `code-syntax-leaf-static`, `code-syntax-leaf`: Updated to use lowlight token classes\n - Removed `prismjs` dependency and related styles\n - Use `lowlight` plugin option instead of `prism` option\n - `code-block-combobox`: add `Auto` language option, change language values to match lowlight\n- `autoformat-plugin`: prevent autoformat on code blocks\n\n```tsx\nimport { all, createLowlight } from 'lowlight';\n\nconst lowlight = createLowlight(all);\n\nCodeBlockPlugin.configure({\n options: {\n lowlight,\n },\n});\n```\n\n### Feburary 21 #19.3\n\n- `image-preview`: prevent block menu on image preivew mask\n- `media-popover`: hide media popover when image preivew is open\n\n### February 18 #19.2\n\nPlate 45 - new comments & suggestions UI\n\n- NEW `block-discussion` as the main container, used in `plate-element`\n- NEW `comment` for individual comment display\n- NEW `comment-create-form` with minimal Plate editor for input\n- Removed legacy components:\n - `comments-popover`\n - `comment-avatar`\n - `comment-reply-items`\n - `comment-value`\n - `comment-resolve-button`\n- NEW `block-suggestion`\n- NEW `suggestion-leaf`\n- NEW `suggestion-line-break`\n- Remove `plate-element`, import `PlateElement` from `@udecode/plate/react` instead. Add in `block-selection-plugins`:\n```tsx\nrender: {\n belowRootNodes: (props) => {\n if (!props.className?.includes('slate-selectable')) return null;\n\n return ;\n },\n},\n```\n\n### February 3 #19.1\n\n- React 19\n- TailwindCSS 4\n- Plate 45\n- Jotai 2\n- Zustand 6\n- `comment-more-dropdown`: remove `useCommentEditButtonState`, `useCommentDeleteButtonState`\n- `image-element`, `media-embed-element`, `media-video-element`, `mode-dropdown-menu`\n - use `const width = useResizableValue('width')`\n- `image-preview`: remove `useScaleInputState`, `useImagePreviewState`\n- `floating-toolbar`: \n - replace `useEventEditorSelectors` with `useEventEditorValue`\n- `media-popover`: \n - replace `floatingMediaActions` with `FloatingMediaStore.set`, \n - replace `useFloatingMediaSelectors` with `useFloatingMediaValue`\n\n## January 2025 #18\n\n### January 23 #18.8\n\n- `table-element`: fix styles, show table border controls when collapsed\n- `table-row-element`: refactor\n- `table-cell-element`: selection bg-brand\n\n### January 21 #18.7\n\n- `table-element`, `table-row-element`: support row dnd and selection\n- `plate-element`: add `blockSelectionClassName` prop\n- `editor`: z-50 for selection area\n- `draggable`: \n - Replace `editor.api.blockSelection.replaceSelectedIds` with `editor.api.blockSelection.clear`\n - Use `TooltipButton` for drag handle\n - Block select on drag handle click\n - Hide drag handle in table cells\n- `column-element`, `table-cell-element`: add `isSelectionAreaVisible` check\n- `block-selection`: hide if dragging\n- Replace `editor.api.blockSelection.addSelectedRow` with `editor.api.blockSelection.set`:\n - `ai-menu`\n - `equation-popover`\n- `align-dropdown-menu`: deprecate \n\n\n### January 18 #18.6\n\n- `inline-equation-element` and `equation-popover`: Fix: When selecting an inline equation, the popover should not open, as it causes the selection to be lost.\n\n### January 17 #18.5\n\n- `emoji-picker-search-bar`: add `autoFocus`\n\n### January 16 #18.4\n\n- `import-toolbar-button` and `export-toolbar-button`: add `markdown` support\n\n### January 14 #18.3\n- `fixed-toolbar-buttons`: add `import-toolbar-button`\n- `indent-fire-marker.tsx` Add `data-plate-prevent-deserialization` to prevent deserialization of the fire marker. Change the `span` tag to `li`.\n- `indent-todo-marker.tsx` change the `span` tag to `li`.\n- `image-element-static.tsx` and `hr-element-static.tsx`: Fix `nodeProps` not being passed to `SlateElement`.\n- `ai-chat-editor`:\n```tsx\nconst aiEditor = usePlateEditor({ plugins });\nuseAIChatEditor(aiEditor, content);\n```\n\n### January 12 #18.2\n\n- `ai-plugins`: remove `createAIEditor`, it's now created in `ai-chat-editor`\n- `ai-chat-editor`: just use `useAIChatEditor` (v42.1)\n- `ai-menu`: avoid collisions, remove `aiEditorRef`\n- `command`: add `focus-visible:outline-none`\n- `editor-static`: update `aiChat` padding\n- `transforms`: fix `insertBlock` used by slash commands: it should insert a new block if the newly inserted block is of the same type as the command.\n- `block-selection-plugins`: update `BlockSelectionPlugin`\n\n```tsx\nBlockSelectionPlugin.configure(({ editor }) => ({\n options: {\n enableContextMenu: true,\n isSelectable: (element, path) => {\n return (\n !['code_line', 'column', 'td'].includes(element.type) &&\n !editor.api.block({ above: true, at: path, match: { type: 'tr' } })\n );\n },\n },\n}))\n```\n\n \n### January 8 #18.1\n\n- v42 migration\n- `table-element`, `table-element-static`\n - Move icons to `table-icons`\n - Remove `colgroup`, col width is now set in `table-cell-element`\n- `table-row-element`: remove `hideBorder` prop\n- `table-cell-element`, `table-cell-element-static`: \n - column hover/resizing state is now using Tailwind instead of JS\n - **Major performance improvement**: all table cells were re-rendering on a single cell change. This is now fixed.\n - React.memo\n- `table-dropdown-menu`:\n - dynamic table insert\n - merge/split cells\n - insert row/col before\n- `tooltip`: add `TooltipButton`\n- `indent-list-toolbar-button`: Remove `IndentListToolbarButton` use `NumberedIndentListToolbarButton` and `BulletedIndentListToolbarButton` instead.\n- `table-dropdown-menu`: new insert table interface.\n- `column-group-element`: fix `ColumnFloatingToolbar` onColumnChange\n\n## December 2024 #17\n\n### December 28 #17.8\n\n- `export-toolbar-button`: add `katex` support\n- `plate-element`: remove `relative` className\n- All components using the `PlateElement` have had redundant `relative` class names removed.\n### December 27 #17.7\n\n- `fixed-toolbar-buttons`: add `font-size-toolbar-button`\n- `floating-toolbar`: add `inline-equation-toolbar-button`\n- `turn-into-dropdown-menu`: Fix: after turn into other block, the editor should regain focus.\n- `insert-dropdown-menu`: add `inline equation` and `equation` & fix the focus issue\n- `slash-input-element`: add `equation` and `inline equation`\n\n### December 23 #17.5\n\n- `table-element`: fix selection\n- before: `isSelectingCell && '[&_*::selection]:bg-none'`\n- after: `isSelectingCell && '[&_*::selection]:!bg-transparent'`\n\n\n### December 21 #17.4\n\nUpdate `tailwind.config.cjs` for better font support in the HTML export:\n\n```ts\nfontFamily: {\n heading: [\n 'var(--font-heading)',\n 'ui-sans-serif',\n '-apple-system',\n 'BlinkMacSystemFont',\n 'Segoe UI Variable Display',\n 'Segoe UI',\n 'Helvetica',\n 'Apple Color Emoji',\n 'Arial',\n 'sans-serif',\n 'Segoe UI Emoji',\n 'Segoe UI Symbol',\n 'Noto Color Emoji',\n ],\n mono: ['var(--font-mono)', ...fontFamily.mono],\n sans: [\n 'var(--font-sans)',\n 'ui-sans-serif',\n '-apple-system',\n 'BlinkMacSystemFont',\n 'Segoe UI Variable Display',\n 'Segoe UI',\n 'Helvetica',\n 'Apple Color Emoji',\n 'Arial',\n 'sans-serif',\n 'Segoe UI Emoji',\n 'Segoe UI Symbol',\n 'Noto Color Emoji',\n ],\n```\n\n\n### December 20 #17.3\n\n- `insertColumnGroup`, `toggleColumnGroup`: use `columns` option instead of `layout` \n- Remove `with-draggables`. Add [`DraggableAboveNodes`](https://github.com/udecode/plate/pull/3878/files#diff-493c12ebed9c3ef9fd8c3a723909b18ad439a448c0132d2d93e5341ee0888ad2) to `draggable`. Add to `DndPlugin` config:\n```tsx\nDndPlugin.configure({ render: { aboveNodes: DraggableAboveNodes } }),\n```\n- `column-element`, `image-element`, `media-video-element`: Remove `useDraggableState`. Use `const { isDragging, previewRef, handleRef } = useDraggable`\n- `column-group-element`: Remove `useColumnState`. Use instead:\n```tsx\nconst columnGroupElement = useElement(ColumnPlugin.key);\n\nconst onColumnChange = (widths: string[]) => {\n setColumns(editor, {\n at: findNodePath(editor, columnGroupElement),\n widths,\n });\n};\n```\n- `export-toolbar-button`: add `exportToHtml`\n\n### December 19 #17.2\n\nPlate 41\n\n- New RSC components for element and leaf components, filename ending with `-static.tsx`. Those are now added along with the default client components.\n- `editor`: add `select-text` to `editorVariants`\n- `date-element`: remove popover when read-only\n- `indent-todo-marker`: use `SlateRenderElementProps` type instead of `PlateRenderElementProps`\n- `hr-element`, `media-audio-element`, `media-embed-element`, `mention-element`: improve cursor styling\n- `media-file-element`: use `` instead of `div` + `onClick`\n- all element and leaf components: `className` prop is now placed before inline prop.\n\n### December 16 #17.1\n\n- `column-element`:\n - Add drag and drop support for columns\n - Add drag handle with tooltip\n - Fix column spacing and padding\n\n- `column-group-element`:\n - Remove gap between columns\n - Remove margin top\n\n- `draggable`:\n - Remove `DraggableProvider` HOC\n - Remove `DropLine` children prop\n\n## November 2024 #16\n\n### November 26 #16.9\n\nhttps://github.com/udecode/plate/pull/3809/files\n- Add `select-editor`, `tag-element`, `label`, `form`\n- Replace `cmdk` dependency with `@udecode/cmdk`. It's a controllable version of `cmdk`.\n- `command`: add variants\n- `editor`: add `select` variant\n- `popover`: add `animate` variant\n\nhttps://github.com/udecode/plate/pull/3807/files\n- `toc-element`: remove `\n \n );\n}\n\nconst emojiCategoryIcons: Record<\n EmojiCategoryList,\n {\n outline: React.ReactElement;\n solid: React.ReactElement; // Needed to add another solid variant - outline will be used for now\n }\n> = {\n activity: {\n outline: (\n \n \n \n \n \n \n ),\n solid: (\n \n \n \n \n \n \n ),\n },\n\n custom: {\n outline: ,\n solid: ,\n },\n\n flags: {\n outline: ,\n solid: ,\n },\n\n foods: {\n outline: ,\n solid: ,\n },\n\n frequent: {\n outline: ,\n solid: ,\n },\n\n nature: {\n outline: ,\n solid: ,\n },\n\n objects: {\n outline: ,\n solid: ,\n },\n\n people: {\n outline: ,\n solid: ,\n },\n\n places: {\n outline: ,\n solid: ,\n },\n\n symbols: {\n outline: ,\n solid: ,\n },\n};\n\nconst emojiSearchIcons = {\n delete: ,\n loupe: ,\n};\n", + "content": "'use client';\n/* eslint-disable react-hooks/refs */\n\nimport * as React from 'react';\n\nimport type { Emoji } from '@emoji-mart/data';\n\nimport {\n type EmojiCategoryList,\n type EmojiIconList,\n type GridRow,\n EmojiSettings,\n} from '@platejs/emoji';\nimport {\n type EmojiDropdownMenuOptions,\n type UseEmojiPickerType,\n useEmojiDropdownMenuState,\n} from '@platejs/emoji/react';\nimport * as Popover from '@radix-ui/react-popover';\nimport {\n AppleIcon,\n ClockIcon,\n CompassIcon,\n FlagIcon,\n LeafIcon,\n LightbulbIcon,\n MusicIcon,\n SearchIcon,\n SmileIcon,\n StarIcon,\n XIcon,\n} from 'lucide-react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@/components/ui/tooltip';\nimport { cn } from '@/lib/utils';\nimport { ToolbarButton } from '@/registry/ui/toolbar';\n\nexport function EmojiToolbarButton({\n options,\n ...props\n}: {\n options?: EmojiDropdownMenuOptions;\n} & React.ComponentPropsWithoutRef) {\n const { emojiPickerState, isOpen, setIsOpen } =\n useEmojiDropdownMenuState(options);\n\n return (\n \n \n \n }\n isOpen={isOpen}\n setIsOpen={setIsOpen}\n >\n \n \n );\n}\n\nexport function EmojiPopover({\n children,\n control,\n isOpen,\n setIsOpen,\n}: {\n children: React.ReactNode;\n control: React.ReactNode;\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n}) {\n return (\n \n {control}\n\n \n {children}\n \n \n );\n}\n\nexport function EmojiPicker({\n clearSearch,\n emoji,\n emojiLibrary,\n focusedCategory,\n hasFound,\n i18n,\n icons = {\n categories: emojiCategoryIcons,\n search: emojiSearchIcons,\n },\n isSearching,\n refs,\n searchResult,\n searchValue,\n setSearch,\n settings = EmojiSettings,\n visibleCategories,\n handleCategoryClick,\n onMouseOver,\n onSelectEmoji,\n}: Omit & {\n icons?: EmojiIconList;\n}) {\n return (\n \n \n \n \n \n \n \n \n );\n}\n\nconst EmojiButton = React.memo(function EmojiButton({\n emoji,\n index,\n onMouseOver,\n onSelect,\n}: {\n emoji: Emoji;\n index: number;\n onMouseOver: (emoji?: Emoji) => void;\n onSelect: (emoji: Emoji) => void;\n}) {\n return (\n onSelect(emoji)}\n onMouseEnter={() => onMouseOver(emoji)}\n onMouseLeave={() => onMouseOver()}\n aria-label={emoji.skins[0].native}\n data-index={index}\n tabIndex={-1}\n type=\"button\"\n >\n \n \n {emoji.skins[0].native}\n \n \n );\n});\n\nconst RowOfButtons = React.memo(function RowOfButtons({\n emojiLibrary,\n row,\n onMouseOver,\n onSelectEmoji,\n}: {\n row: GridRow;\n} & Pick<\n UseEmojiPickerType,\n 'emojiLibrary' | 'onMouseOver' | 'onSelectEmoji'\n>) {\n return (\n
\n {row.elements.map((emojiId, index) => (\n \n ))}\n
\n );\n});\n\nfunction EmojiPickerContent({\n emojiLibrary,\n i18n,\n isSearching = false,\n refs,\n searchResult,\n settings = EmojiSettings,\n visibleCategories,\n onMouseOver,\n onSelectEmoji,\n}: Pick<\n UseEmojiPickerType,\n | 'emojiLibrary'\n | 'i18n'\n | 'isSearching'\n | 'onMouseOver'\n | 'onSelectEmoji'\n | 'refs'\n | 'searchResult'\n | 'settings'\n | 'visibleCategories'\n>) {\n const getRowWidth = settings.perLine.value * settings.buttonSize.value;\n\n const isCategoryVisible = React.useCallback(\n (categoryId: any) =>\n visibleCategories.has(categoryId)\n ? visibleCategories.get(categoryId)\n : false,\n [visibleCategories]\n );\n\n const EmojiList = React.useCallback(\n () =>\n emojiLibrary\n .getGrid()\n .sections()\n .map(({ id: categoryId }) => {\n const section = emojiLibrary.getGrid().section(categoryId);\n const { buttonSize } = settings;\n\n return (\n \n
\n {i18n.categories[categoryId]}\n
\n \n {isCategoryVisible(categoryId) &&\n section\n .getRows()\n .map((row: GridRow) => (\n \n ))}\n \n \n );\n }),\n [\n emojiLibrary,\n getRowWidth,\n i18n.categories,\n isCategoryVisible,\n onSelectEmoji,\n onMouseOver,\n settings,\n ]\n );\n\n const SearchList = React.useCallback(\n () => (\n
\n
\n {i18n.searchResult}\n
\n
\n {searchResult.map((emoji: Emoji, index: number) => (\n \n ))}\n
\n
\n ),\n [\n emojiLibrary,\n getRowWidth,\n i18n.searchResult,\n searchResult,\n onSelectEmoji,\n onMouseOver,\n ]\n );\n\n return (\n \n
\n {isSearching ? SearchList() : EmojiList()}\n
\n \n );\n}\n\nfunction EmojiPickerSearchBar({\n children,\n i18n,\n searchValue,\n setSearch,\n}: {\n children: React.ReactNode;\n} & Pick) {\n return (\n
\n
\n setSearch(event.target.value)}\n placeholder={i18n.search}\n aria-label=\"Search\"\n autoComplete=\"off\"\n type=\"text\"\n autoFocus\n />\n {children}\n
\n
\n );\n}\n\nfunction EmojiPickerSearchAndClear({\n clearSearch,\n i18n,\n searchValue,\n}: Pick) {\n return (\n
\n \n {emojiSearchIcons.loupe}\n
\n {searchValue && (\n \n {emojiSearchIcons.delete}\n \n )}\n \n );\n}\n\nfunction EmojiPreview({ emoji }: Pick) {\n return (\n
\n
\n {emoji?.skins[0].native}\n
\n
\n
{emoji?.name}
\n
{`:${emoji?.id}:`}
\n
\n
\n );\n}\n\nfunction NoEmoji({ i18n }: Pick) {\n return (\n
\n
😢
\n
\n
\n {i18n.searchNoResultsTitle}\n
\n
{i18n.searchNoResultsSubtitle}
\n
\n
\n );\n}\n\nfunction PickAnEmoji({ i18n }: Pick) {\n return (\n
\n
☝️
\n
\n
{i18n.pick}
\n
\n
\n );\n}\n\nfunction EmojiPickerPreview({\n emoji,\n hasFound = true,\n i18n,\n isSearching = false,\n ...props\n}: Pick) {\n const showPickEmoji = !emoji && (!isSearching || hasFound);\n const showNoEmoji = isSearching && !hasFound;\n const showPreview = emoji && !showNoEmoji && !showNoEmoji;\n\n return (\n <>\n {showPreview && }\n {showPickEmoji && }\n {showNoEmoji && }\n \n );\n}\n\nfunction EmojiPickerNavigation({\n emojiLibrary,\n focusedCategory,\n i18n,\n icons,\n onClick,\n}: {\n onClick: (id: EmojiCategoryList) => void;\n} & Pick<\n UseEmojiPickerType,\n 'emojiLibrary' | 'focusedCategory' | 'i18n' | 'icons'\n>) {\n return (\n \n \n
\n {emojiLibrary\n .getGrid()\n .sections()\n .map(({ id }) => (\n \n \n {\n onClick(id);\n }}\n aria-label={i18n.categories[id]}\n type=\"button\"\n >\n \n {icons.categories[id].outline}\n \n \n \n \n {i18n.categories[id]}\n \n \n ))}\n
\n \n
\n );\n}\n\nconst emojiCategoryIcons: Record<\n EmojiCategoryList,\n {\n outline: React.ReactElement;\n solid: React.ReactElement; // Needed to add another solid variant - outline will be used for now\n }\n> = {\n activity: {\n outline: (\n \n \n \n \n \n \n ),\n solid: (\n \n \n \n \n \n \n ),\n },\n\n custom: {\n outline: ,\n solid: ,\n },\n\n flags: {\n outline: ,\n solid: ,\n },\n\n foods: {\n outline: ,\n solid: ,\n },\n\n frequent: {\n outline: ,\n solid: ,\n },\n\n nature: {\n outline: ,\n solid: ,\n },\n\n objects: {\n outline: ,\n solid: ,\n },\n\n people: {\n outline: ,\n solid: ,\n },\n\n places: {\n outline: ,\n solid: ,\n },\n\n symbols: {\n outline: ,\n solid: ,\n },\n};\n\nconst emojiSearchIcons = {\n delete: ,\n loupe: ,\n};\n", "type": "registry:ui" } ], diff --git a/apps/www/public/r/installation-docs-docs.json b/apps/www/public/r/installation-docs-docs.json index e03f188f8c..28e0935003 100644 --- a/apps/www/public/r/installation-docs-docs.json +++ b/apps/www/public/r/installation-docs-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/installation/docs.mdx", - "content": "---\ntitle: Local Docs\ndescription: Set up Plate's documentation locally for version-controlled, AI-enhanced development.\n---\n\nHost Plate's documentation locally to integrate it directly with your project. This setup ensures your team works with documentation that matches your Plate version while enabling AI tools to better understand and assist with your codebase.\n\n## Why Host Documentation Locally?\n\nLocal documentation provides distinct advantages over external sites:\n\n* **Version Lock-In:** Keep documentation synchronized with your project's Plate version. Avoid confusion from features or APIs in newer, unadopted Plate releases.\n* **AI-Ready Development:**\n * **Better than `llms.txt`:** While dumping docs into a single text file is common for LLM context, this breaks down with large documentation (exceeding typical 100k token limits). Our structured, local setup lets AI tools access exactly what they need.\n * **Direct Access for AI Tools:** Your AI-assisted IDE gets direct access to version-specific documentation, enabling accurate code generation and contextual help with your Plate setup.\n * **Structured for AI Tasks:** AI tools can help with tasks like translating docs, creating summaries, or updating documentation between Plate versions.\n* **Customization & Control:** Modify documentation appearance and structure to match your project needs.\n* **Easy Updates:** Treat documentation like code - review, update, and version control it with your codebase.\n* **Fast Access:** Get reliable, quick access to documentation.\n* **Central Hub:** Keep Plate docs alongside documentation for other libraries in one place.\n\n## Setup Guide\n\nThere are two ways to set up local documentation:\n\n### Option 1: Docs App\n\nThis option sets up a complete documentation site using Fumadocs, providing a searchable, navigable interface.\n\n\n\n#### Create Fumadocs App\n\nSet up a Fumadocs app which provides the Next.js-based framework for your documentation site:\n\n```bash\npnpm create fumadocs-app\n```\n\nDuring setup:\n- **Name:** Enter `docs` when prompted\n- **Choose your preferred options** (defaults work well)\n- The wizard will create a `docs` directory with all necessary files\n\nNavigate to your newly created `docs` directory:\n\n```bash\ncd docs\n```\n\nFor detailed Fumadocs setup, see the [Fumadocs UI documentation](https://fumadocs.dev/docs/ui).\n\n#### Create `components.json`\n\nIn your docs directory, create a `components.json` file. You have two options:\n\n**Option A: Command line**\n\n```bash\necho '{\\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\\n \"style\": \"new-york\",\\n \"rsc\": true,\\n \"tsx\": true,\\n \"tailwind\": {\\n \"config\": \"\",\\n \"css\": \"app/global.css\",\\n \"baseColor\": \"neutral\",\\n \"cssVariables\": true,\\n \"prefix\": \"\"\\n },\\n \"aliases\": {\\n \"components\": \"@/components\",\\n \"utils\": \"@/lib/utils\",\\n \"ui\": \"@/components/ui\",\\n \"lib\": \"@/lib\",\\n \"hooks\": \"@/hooks\"\\n },\\n \"iconLibrary\": \"lucide\"\\n}' > components.json\n```\n\n**Option B: Copy and paste**\n\nCreate a new file called `components.json` in your docs directory with this content:\n\n```json\n{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"\",\n \"css\": \"app/global.css\",\n \"baseColor\": \"neutral\",\n \"cssVariables\": true,\n \"prefix\": \"\"\n },\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n },\n \"iconLibrary\": \"lucide\"\n}\n```\n\n#### Add Plate Documentation\n\nNow, fetch the Plate documentation files and necessary MDX components.\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/fumadocs\n```\n\n\n The command above installs the **latest** Plate documentation. For projects on older Plate versions (minimum `v48.0.0`), specify your version:\n\n 1. Go to [Plate's public registry on GitHub](https://github.com/udecode/plate/tree/main/apps/www/public/r).\n 2. Use the tag switcher to select your Plate version.\n 3. Copy the versioned URL (e.g., [v48](https://github.com/udecode/plate/tree/%40udecode/plate%4048.0.5/apps/www/public/r)).\n 4. Replace `https://platejs.org/r/` in the `shadcn` command with this URL.\n\n\n#### Run Fumadocs App\n\nStart the development server:\n\n```bash\npnpm run dev\n```\n\nYour documentation site will be available at:\n- Plate docs: `http://localhost:3000/docs/plate`\n\n#### Customize\n\nEnhance your docs with [Fumadocs features](https://fumadocs.dev/docs/ui#writing-content).\n\n\n\n### Option 2: MDX Files Only\n\nIf you just want the documentation files without setting up a full site, you can add them directly to your existing project:\n\n```bash\n# Run from your project root (wherever you want the docs)\nnpx shadcn@latest add https://platejs.org/r/docs\n```\n\nThis will:\n- Install MDX documentation files in your project (typically in a `docs/` or similar directory)\n- Skip the Fumadocs setup entirely\n- Give you raw MDX files to use however you need\n\nUse cases:\n- Reference documentation directly in your codebase\n- Integrate with your existing documentation setup\n- Make docs available to AI tools for better context\n\n## Advanced Integration\n\n### MCP Integration\n\nEnable AI tools to work with your local documentation by adding the Plate server to your `.cursor/mcp.json` (or equivalent).\n\n```json\n{\n \"mcpServers\": {\n \"plate\": {\n \"description\": \"Plate editors, plugins, components and documentation\",\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"shadcn@canary\", \"registry:mcp\"],\n \"env\": {\n \"REGISTRY_URL\": \"https://platejs.org/r/registry.json\"\n }\n }\n }\n}\n```\n\nYour AI tools can then:\n* Access documentation context for better code assistance\n* Help manage and update documentation\n* Generate code with proper imports and configurations\n* Assist with editor setup and feature integration\n\nSee the [MCP Guide](/docs/installation/mcp) for more details.\n\n\n Once configured, try asking your AI:\n ```bash\n \"Help me understand how the Plate AI plugin works\"\n \"How to create a custom plugin?\"\n \"What's new in the latest Plate version?\"\n ```\n\n\n### Centralizing Multiple Documentations\n\nYour `content/docs/` directory can host documentation for multiple libraries. Replicate the process for Plate to add docs for other internal or external tools:\n\n```plaintext\ncontent/\n└── docs/\n ├── plate/ # Plate documentation\n │ └── ...\n ├── other-library/ # Documentation for another library\n │ └── ...\n └── index.mdx # Main landing page for all docs\n```\n\nThis creates a unified, version-controlled knowledge base for your project stack.", + "content": "---\ntitle: Local Docs\ndescription: Set up Plate's documentation locally for version-controlled, AI-enhanced development.\n---\n\nHost Plate's documentation locally to integrate it directly with your project. This setup ensures your team works with documentation that matches your Plate version while enabling AI tools to better understand and assist with your codebase.\n\n## Why Host Documentation Locally?\n\nLocal documentation provides distinct advantages over external sites:\n\n* **Version Lock-In:** Keep documentation synchronized with your project's Plate version. Avoid confusion from features or APIs in newer, unadopted Plate releases.\n* **AI-Ready Development:**\n * **Better than `llms.txt`:** While dumping docs into a single text file is common for LLM context, this breaks down with large documentation (exceeding typical 100k token limits). Our structured, local setup lets AI tools access exactly what they need.\n * **Direct Access for AI Tools:** Your AI-assisted IDE gets direct access to version-specific documentation, enabling accurate code generation and contextual help with your Plate setup.\n * **Structured for AI Tasks:** AI tools can help with tasks like translating docs, creating summaries, or updating documentation between Plate versions.\n* **Customization & Control:** Modify documentation appearance and structure to match your project needs.\n* **Easy Updates:** Treat documentation like code - review, update, and version control it with your codebase.\n* **Fast Access:** Get reliable, quick access to documentation.\n* **Central Hub:** Keep Plate docs alongside documentation for other libraries in one place.\n\n## Setup Guide\n\nThere are two ways to set up local documentation:\n\n### Option 1: Docs App\n\nThis option sets up a complete documentation site using Fumadocs, providing a searchable, navigable interface.\n\n\n\n#### Create Fumadocs App\n\nSet up a Fumadocs app which provides the Next.js-based framework for your documentation site:\n\n```bash\npnpm create fumadocs-app\n```\n\nDuring setup:\n- **Name:** Enter `docs` when prompted\n- **Choose your preferred options** (defaults work well)\n- The wizard will create a `docs` directory with all necessary files\n\nNavigate to your newly created `docs` directory:\n\n```bash\ncd docs\n```\n\nFor detailed Fumadocs setup, see the [Fumadocs UI documentation](https://fumadocs.dev/docs/ui).\n\n#### Create `components.json`\n\nIn your docs directory, create a `components.json` file. You have two options:\n\n**Option A: Command line**\n\n```bash\necho '{\\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\\n \"style\": \"new-york\",\\n \"rsc\": true,\\n \"tsx\": true,\\n \"tailwind\": {\\n \"config\": \"\",\\n \"css\": \"app/global.css\",\\n \"baseColor\": \"neutral\",\\n \"cssVariables\": true,\\n \"prefix\": \"\"\\n },\\n \"aliases\": {\\n \"components\": \"@/components\",\\n \"utils\": \"@/lib/utils\",\\n \"ui\": \"@/components/ui\",\\n \"lib\": \"@/lib\",\\n \"hooks\": \"@/hooks\"\\n },\\n \"iconLibrary\": \"lucide\"\\n}' > components.json\n```\n\n**Option B: Copy and paste**\n\nCreate a new file called `components.json` in your docs directory with this content:\n\n```json\n{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"\",\n \"css\": \"app/global.css\",\n \"baseColor\": \"neutral\",\n \"cssVariables\": true,\n \"prefix\": \"\"\n },\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n },\n \"iconLibrary\": \"lucide\"\n}\n```\n\n#### Add Plate Documentation\n\nNow, fetch the Plate documentation files and necessary MDX components.\n\n```bash\nnpx shadcn@latest add @plate/fumadocs\n```\n\n\n The command above installs the **latest** Plate documentation. For projects on older Plate versions (minimum `v48.0.0`), specify your version:\n\n 1. Go to [Plate's public registry on GitHub](https://github.com/udecode/plate/tree/main/apps/www/public/r).\n 2. Use the tag switcher to select your Plate version.\n 3. Copy the versioned URL (e.g., [v48](https://github.com/udecode/plate/tree/%40udecode/plate%4048.0.5/apps/www/public/r)).\n 4. Replace `https://platejs.org/r/` in the `shadcn` command with this URL.\n\n\n#### Run Fumadocs App\n\nStart the development server:\n\n```bash\npnpm run dev\n```\n\nYour documentation site will be available at:\n- Plate docs: `http://localhost:3000/docs/plate`\n\n#### Customize\n\nEnhance your docs with [Fumadocs features](https://fumadocs.dev/docs/ui#writing-content).\n\n\n\n### Option 2: MDX Files Only\n\nIf you just want the documentation files without setting up a full site, you can add them directly to your existing project:\n\n```bash\n# Run from your project root (wherever you want the docs)\nnpx shadcn@latest add @plate/docs\n```\n\nThis will:\n- Install MDX documentation files in your project (typically in a `docs/` or similar directory)\n- Skip the Fumadocs setup entirely\n- Give you raw MDX files to use however you need\n\nUse cases:\n- Reference documentation directly in your codebase\n- Integrate with your existing documentation setup\n- Make docs available to AI tools for better context\n\n## Advanced Integration\n\n### MCP Integration\n\nEnable AI tools to work with your local documentation by adding the Plate server to your `.cursor/mcp.json` (or equivalent).\n\n```json\n{\n \"mcpServers\": {\n \"shadcn\": {\n \"description\": \"Shadcn and Plate MCP\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\nYour AI tools can then:\n* Access documentation context for better code assistance\n* Help manage and update documentation\n* Generate code with proper imports and configurations\n* Assist with editor setup and feature integration\n\nSee the [MCP Guide](/docs/installation/mcp) for more details.\n\n\n Once configured, try asking your AI:\n ```bash\n \"Help me understand how the Plate AI plugin works\"\n \"How to create a custom plugin?\"\n \"What's new in the latest Plate version?\"\n ```\n\n\n### Centralizing Multiple Documentations\n\nYour `content/docs/` directory can host documentation for multiple libraries. Replicate the process for Plate to add docs for other internal or external tools:\n\n```plaintext\ncontent/\n└── docs/\n ├── plate/ # Plate documentation\n │ └── ...\n ├── other-library/ # Documentation for another library\n │ └── ...\n └── index.mdx # Main landing page for all docs\n```\n\nThis creates a unified, version-controlled knowledge base for your project stack.", "type": "registry:file", "target": "content/docs/plate/installation/docs.mdx" } diff --git a/apps/www/public/r/installation-mcp-docs.json b/apps/www/public/r/installation-mcp-docs.json index e95328f6df..7294653a91 100644 --- a/apps/www/public/r/installation-mcp-docs.json +++ b/apps/www/public/r/installation-mcp-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/installation/mcp.mdx", - "content": "---\ntitle: MCP Server\ndescription: Use the Model Context Protocol with Plate.\n---\n\nPlate has an official MCP server. This lets AI tools understand and work with our rich ecosystem of editor templates, plugin configurations, UI components, and documentation.\n\n## What is MCP?\n\nThe Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs. This is useful for Plate because you can now give your AI-assisted IDE direct access to hundreds of Plate resources.\n\n## Using MCP with Plate\n\nYour AI can now:\n\n- Access to all our editor templates, plugins, and UI components\n- Access our complete documentation, including guides and API references\n- Generate code with the right imports and configurations\n- Help with setting up full editor instances or specific features\n- Keep your Plate configurations and components up to date\n\nTry asking your AI:\n\n```bash\n\"Set up a Plate editor with basic formatting and table support\"\n\"Help me understand how the Plate AI plugin works\"\n\"Update my editor components to the latest version\"\n```\n\n## How it works\n\nThe Plate ecosystem provides structured information that MCP-enabled tools can read from a unified registry that includes:\n\n- Editor templates and plugin configurations\n- UI components and their dependencies\n- Documentation files and migration guides\n- API references and examples\n\nThis comprehensive registry ensures AI tools have complete context for both code generation and understanding Plate's features.\n\n## Local Documentation\n\nFor teams working with Plate, integrating local documentation is key to maximizing the benefits of MCP. We recommend following our [Local Docs](/docs/installation/docs) guide to set this up. This approach offers several advantages for AI-powered development:\n\n- **Version-Specific Context:** AI tools gain direct access to documentation that precisely matches your project's Plate version, ensuring relevant and accurate assistance.\n- **Superior to `llms.txt`:** Unlike simply dumping documentation into a text file (which can struggle with large volumes and context limits), a structured local setup allows AI to efficiently access the specific information it needs.\n- **Integrated Workflow:** Documentation becomes a part of your codebase, simplifying updates, version control, and team collaboration.\n- **AI-Ready:** A well-structured local documentation allows AI to more effectively assist with tasks such as generating code, creating summaries, or explaining complex Plate features within the context of your project.\n\n## Setup MCP\n\nFollow these steps to set up MCP with Plate:\n\n### Step 1: Start from our basic template\n\nRun this command to initialize your project with the basic editor template:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/editor-basic\n```\n\n### Step 2: Add MCP config\n\nConfigure your registry in your `components.json` file:\n\n```json\n{\n \"registries\": {\n \"@platejs\": \"https://platejs.org/r/{name}.json\"\n }\n}\n```\n\n### Step 3: Configure your IDE\n\nChoose the configuration for your IDE:\n\n#### Cursor\n\nCopy and paste into `.cursor/mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"plate\": {\n \"description\": \"Plate editors, plugins and components\",\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### VS Code\n\nCopy and paste into `.vscode/mcp.json` in your workspace:\n\n```json\n{\n \"servers\": {\n \"plate\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### Claude\n\nCopy and paste into `.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"plate\": {\n \"description\": \"Plate editors, plugins and components\",\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### CodeX\n\n1. Open or create the file `~/.codex/config.toml`\n2. Add the following configuration:\n\n```toml\n[mcp_servers.plate]\ncommand = \"npx\"\nargs = [\"shadcn@latest\", \"mcp\"]\n```\n\n3. Restart CodeX to load the MCP server\n\n## Best Practices\n\n1. **Local Documentation:** Set up local documentation to give AI tools version-specific context. This ensures more accurate assistance, especially for larger projects.\n2. **AI-Assisted Development:** Let AI handle editor setup, plugin integration, and component additions.\n3. **Manual Fallback:** Use the [shadcn CLI](/docs/components/cli) for manual additions when needed (e.g., with small models or outdated documentation).\n4. **Stay Updated:** Keep both your Plate components and local documentation in sync. Check our [changelog](/docs/components/changelog) regularly or ask your AI about updates.\n", + "content": "---\ntitle: MCP Server\ndescription: Use the Model Context Protocol with Plate.\n---\n\nPlate has an official MCP server. This lets AI tools understand and work with our rich ecosystem of editor templates, plugin configurations, UI components, and documentation.\n\n## What is MCP?\n\nThe Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs. This is useful for Plate because you can now give your AI-assisted IDE direct access to hundreds of Plate resources.\n\n## Using MCP with Plate\n\nYour AI can now:\n\n- Access to all our editor templates, plugins, and UI components\n- Access our complete documentation, including guides and API references\n- Generate code with the right imports and configurations\n- Help with setting up full editor instances or specific features\n- Keep your Plate configurations and components up to date\n\nTry asking your AI:\n\n```bash\n\"Set up a Plate editor with basic formatting and table support\"\n\"Help me understand how the Plate AI plugin works\"\n\"Update my editor components to the latest version\"\n```\n\n## How it works\n\nThe Plate ecosystem provides structured information that MCP-enabled tools can read from a unified registry that includes:\n\n- Editor templates and plugin configurations\n- UI components and their dependencies\n- Documentation files and migration guides\n- API references and examples\n\nThis comprehensive registry ensures AI tools have complete context for both code generation and understanding Plate's features.\n\n## Local Documentation\n\nFor teams working with Plate, integrating local documentation is key to maximizing the benefits of MCP. We recommend following our [Local Docs](/docs/installation/docs) guide to set this up. This approach offers several advantages for AI-powered development:\n\n- **Version-Specific Context:** AI tools gain direct access to documentation that precisely matches your project's Plate version, ensuring relevant and accurate assistance.\n- **Superior to `llms.txt`:** Unlike simply dumping documentation into a text file (which can struggle with large volumes and context limits), a structured local setup allows AI to efficiently access the specific information it needs.\n- **Integrated Workflow:** Documentation becomes a part of your codebase, simplifying updates, version control, and team collaboration.\n- **AI-Ready:** A well-structured local documentation allows AI to more effectively assist with tasks such as generating code, creating summaries, or explaining complex Plate features within the context of your project.\n\n## Setup MCP\n\nFollow these steps to set up MCP with Plate:\n\n### Step 1: Start from our basic template\n\nRun this command to initialize your project with the basic editor template:\n\n```bash\nnpx shadcn@latest add @plate/editor-basic\n```\n\n### Step 2: Add MCP config\n\nConfigure your registry in your `components.json` file:\n\n```json\n{\n \"registries\": {\n \"@platejs\": \"https://platejs.org/r/{name}.json\"\n }\n}\n```\n\n### Step 3: Configure your IDE\n\nChoose the configuration for your IDE:\n\n#### Cursor\n\nCopy and paste into `.cursor/mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"plate\": {\n \"description\": \"Plate editors, plugins and components\",\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### VS Code\n\nCopy and paste into `.vscode/mcp.json` in your workspace:\n\n```json\n{\n \"servers\": {\n \"plate\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### Claude\n\nCopy and paste into `.mcp.json`:\n\n```json\n{\n \"mcpServers\": {\n \"plate\": {\n \"description\": \"Plate editors, plugins and components\",\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\n \"shadcn@latest\",\n \"mcp\"\n ]\n }\n }\n}\n```\n\n#### CodeX\n\n1. Open or create the file `~/.codex/config.toml`\n2. Add the following configuration:\n\n```toml\n[mcp_servers.plate]\ncommand = \"npx\"\nargs = [\"shadcn@latest\", \"mcp\"]\n```\n\n3. Restart CodeX to load the MCP server\n\n## Best Practices\n\n1. **Local Documentation:** Set up local documentation to give AI tools version-specific context. This ensures more accurate assistance, especially for larger projects.\n2. **AI-Assisted Development:** Let AI handle editor setup, plugin integration, and component additions.\n3. **Manual Fallback:** Use the [shadcn CLI](/docs/components/cli) for manual additions when needed (e.g., with small models or outdated documentation).\n4. **Stay Updated:** Keep both your Plate components and local documentation in sync. Check our [changelog](/docs/components/changelog) regularly or ask your AI about updates.\n", "type": "registry:file", "target": "content/docs/plate/installation/mcp.mdx" } diff --git a/apps/www/public/r/installation-next-docs.json b/apps/www/public/r/installation-next-docs.json index e4bb786917..0def06bfad 100644 --- a/apps/www/public/r/installation-next-docs.json +++ b/apps/www/public/r/installation-next-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/installation/next.mdx", - "content": "---\ntitle: Next.js\ndescription: Install and configure Plate UI for Next.js\n---\n\n\n Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation/next) and [Plate UI](/docs/installation/plate-ui).\n\n\nThis guide walks you through incrementally building a Plate editor in your Next.js application.\n\n\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/editor\n```\n\nNext, create a basic editor page. This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\"\n'use client';\n\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor(); // Initializes the editor instance\n\n return (\n {/* Provides editor context */}\n {/* Styles the editor area */}\n \n \n \n );\n}\n```\n\n\n `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n\n\n\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/basic-nodes-kit https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button\n```\n\n\n The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n\n\nUpdate your editor page to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {4,6-10,17-18,20-33,37-38,43-47}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n {\n type: 'p',\n children: [\n { text: 'Hello! Try out the ' },\n { text: 'bold', bold: true },\n { text: ', ' },\n { text: 'italic', italic: true },\n { text: ', and ' },\n { text: 'underline', underline: true },\n { text: ' formatting.' },\n ],\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n value: initialValue, // Set initial content\n });\n\n return (\n \n \n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {7,9-11,20,23,25,28-35,52-55,63-67}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: initialValue,\n });\n\n return (\n \n \n {/* Element Toolbar Buttons */}\n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n {/* Mark Toolbar Buttons */}\n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n\n Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n ```bash\n npx shadcn@latest add https://platejs.org/r/editor-basic\n ```\n This handles much of the boilerplate for you.\n\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value client-side.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {57-60,66-68,78-84}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: () => {\n const savedValue = localStorage.getItem('installation-next-demo');\n return savedValue ? JSON.parse(savedValue) : initialValue;\n },\n });\n\n return (\n {\n localStorage.setItem('installation-next-demo', JSON.stringify(value));\n }}\n >\n \n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n B\n I\n U\n
\n editor.tf.setValue(initialValue)}\n >\n Reset\n \n \n \n \n \n \n );\n}\n```\n\n\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in Next.js.\n\nTo further enhance your editor:\n\n* **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more.\n* **Add Plugins:** Integrate features like [Tables](/docs/table), [Mentions](/docs/mention), [AI](/docs/ai), or [Markdown](/docs/markdown).\n* **Use Editor Blocks:** Quickly set up pre-configured editors:\n * Basic editor: `npx shadcn@latest add https://platejs.org/r/editor-basic`\n * AI-powered editor: `npx shadcn@latest add https://platejs.org/r/editor-ai`\n* **Learn More:**\n * [Editor Configuration](/docs/editor)\n * [Plugin Configuration](/docs/plugin)\n * [Plugin Components](/docs/plugin-components)\n\n\n", + "content": "---\ntitle: Next.js\ndescription: Install and configure Plate UI for Next.js\n---\n\n\n Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation/next) and [Plate UI](/docs/installation/plate-ui).\n\n\nThis guide walks you through incrementally building a Plate editor in your Next.js application.\n\n\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add @plate/editor\n```\n\nNext, create a basic editor page. This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\"\n'use client';\n\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor(); // Initializes the editor instance\n\n return (\n {/* Provides editor context */}\n {/* Styles the editor area */}\n \n \n \n );\n}\n```\n\n\n `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n\n\n\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\n\n The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n\n\nUpdate your editor page to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {4,6-10,17-18,20-33,37-38,43-47}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n {\n type: 'p',\n children: [\n { text: 'Hello! Try out the ' },\n { text: 'bold', bold: true },\n { text: ', ' },\n { text: 'italic', italic: true },\n { text: ', and ' },\n { text: 'underline', underline: true },\n { text: ' formatting.' },\n ],\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n value: initialValue, // Set initial content\n });\n\n return (\n \n \n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {7,9-11,20,23,25,28-35,52-55,63-67}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: initialValue,\n });\n\n return (\n \n \n {/* Element Toolbar Buttons */}\n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n {/* Mark Toolbar Buttons */}\n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n\n Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n ```bash\n npx shadcn@latest add @plate/editor-basic\n ```\n This handles much of the boilerplate for you.\n\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value client-side.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {57-60,66-68,78-84}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function MyEditorPage() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: () => {\n const savedValue = localStorage.getItem('installation-next-demo');\n return savedValue ? JSON.parse(savedValue) : initialValue;\n },\n });\n\n return (\n {\n localStorage.setItem('installation-next-demo', JSON.stringify(value));\n }}\n >\n \n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n B\n I\n U\n
\n editor.tf.setValue(initialValue)}\n >\n Reset\n \n \n \n \n \n \n );\n}\n```\n\n\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in Next.js.\n\nTo further enhance your editor:\n\n* **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more.\n* **Add Plugins:** Integrate features like [Tables](/docs/table), [Mentions](/docs/mention), [AI](/docs/ai), or [Markdown](/docs/markdown).\n* **Use Editor Blocks:** Quickly set up pre-configured editors:\n * Basic editor: `npx shadcn@latest add @plate/editor-basic`\n * AI-powered editor: `npx shadcn@latest add @plate/editor-ai`\n* **Learn More:**\n * [Editor Configuration](/docs/editor)\n * [Plugin Configuration](/docs/plugin)\n * [Plugin Components](/docs/plugin-components)\n\n\n", "type": "registry:file", "target": "content/docs/plate/installation/next.mdx" } diff --git a/apps/www/public/r/installation-plate-ui-docs.json b/apps/www/public/r/installation-plate-ui-docs.json index 4b39b0e5ac..1d07769281 100644 --- a/apps/www/public/r/installation-plate-ui-docs.json +++ b/apps/www/public/r/installation-plate-ui-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/installation/plate-ui.mdx", - "content": "---\ntitle: Plate UI Installation\ndescription: How to set up Plate UI in your project.\n---\n\nThis guide details how to install Plate UI, the component layer for Plate. Choose the method that suits your project:\n\n* **CLI Installation (Recommended):** Quick setup using the shadcn CLI.\n* **Manual Installation:** For more control over the setup process.\n\n## CLI Installation (Recommended)\n\nThis is the fastest way to integrate Plate UI's core dependencies and base styles.\n\n\n\n### Install Plate UI\n\n\n If you are not using Next.js, refer to the official [shadcn/ui installation guides](https://ui.shadcn.com/docs/installation) for your specific framework before proceeding.\n\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/plate-ui\n```\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n* **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n* **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n\n\n## Manual Installation\n\nIf you prefer a step-by-step approach or are not using the shadcn CLI, follow these steps:\n\n\n\n### Install Plate\n\n```bash\nnpm install platejs\n```\n\n\n When manually installing Plate, you'll need to add the specific packages for each plugin you want to use. For example, if you want to use the `AIMenu` component, you would need `@platejs/ai` and its dependencies. Check each component's documentation for its required packages, or use the CLI to automatically install the required packages.\n\n\n### Configure CSS Variables\n\nAdd the following CSS variables to your global stylesheet:\n\n```css title=\"app/globals.css\"\n:root {\n /* Base brand color for Plate UI components */\n --brand: oklch(0.623 0.214 259.815);\n}\n\n.dark {\n --brand: oklch(0.707 0.165 254.624);\n}\n```\n\n### Add Components\n\nWith Plate UI's core installed, you can now add individual Plate UI components to build your editor interface. For example, to add the `FixedToolbar` and a `MarkToolbarButton`:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button\n```\n\nImport and use them in your editor:\n\n```tsx showLineNumbers title=\"components/editor.tsx\"\nimport { Plate } from \"platejs/react\";\nimport { FixedToolbar } from \"@/components/ui/fixed-toolbar\";\nimport { MarkToolbarButton } from \"@/components/ui/mark-toolbar-button\";\n// ... other imports\n\nexport function MyEditor() {\n // ... editor setup\n return (\n \n \n B\n {/* ... other toolbar buttons ... */}\n \n {/* ... Editor component ... */}\n \n );\n}\n```\n\nExplore the available [UI Components](/docs/components) and [Plugin Components](/docs/plugin-components) to customize your editor nodes (like paragraphs, headings, etc.) and build a feature-rich editing experience.\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n* **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n* **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n\n\n## Component Types\n\nWhen installing Plate UI components, you'll encounter different types of components with consistent naming patterns. Here's what they mean:\n\n### Feature Kits\n\nFeature kits are the easiest way to add complete functionality to your editor. They bundle all necessary plugin configurations, UI components (including node renderers and toolbar items), and their underlying npm dependencies for a specific feature.\n\n```bash\n# Install the complete AI feature suite, including configuration and UI\nnpx shadcn@latest add https://platejs.org/r/ai-kit\n\n# Install drag and drop functionality with all its parts\nnpx shadcn@latest add https://platejs.org/r/dnd-kit\n```\n\n\n When in doubt about what individual components you need, start with a feature kit. It's designed to provide a working feature out-of-the-box. You can always customize or remove parts later.\n\n\n### Components\n\n#### Node Components\n\nThese components are responsible for rendering specific types of content (elements or leaves) in your editor. If you need to create your own or customize existing ones, see the [Plugin Components guide](/docs/plugin-components).\n\n```bash\n# Install the component for rendering blockquotes\nnpx shadcn@latest add https://platejs.org/r/blockquote-node\n\n# Install the component for rendering code text\nnpx shadcn@latest add https://platejs.org/r/code-node\n```\n\n#### Toolbar Components\n\nToolbar components add interactive controls like buttons and dropdowns to your editor's toolbar.\n\n```bash\n# Add an alignment dropdown for your toolbar\nnpx shadcn@latest add https://platejs.org/r/align-toolbar-button\n\n# Add a toolbar button for AI features\nnpx shadcn@latest add https://platejs.org/r/ai-toolbar-button\n\n# Add a color picker dropdown for your toolbar\nnpx shadcn@latest add https://platejs.org/r/font-color-toolbar-button\n```\n\n#### Other Components\n\nFinally, Plate UI includes more advanced editor components as overlays, menus, block wrappers, etc.\n\n```bash\n# Install a menu for AI features\nnpx shadcn@latest add https://platejs.org/r/ai-menu\n\n# Install a draggable component for reordering blocks\nnpx shadcn@latest add https://platejs.org/r/block-draggable\n```\n\nThese components are typically included as part of their respective Feature Kits but can be installed individually for custom setups.\n\n### Editor Templates\n\nThese are pre-configured editor setups, often tailored for specific use cases or as a comprehensive starting point. You can explore all available [Editor Templates](/editors).\n\n```bash\n# Install an AI-enabled editor template\nnpx shadcn@latest add https://platejs.org/r/editor-ai\n\n# Install a basic editor template\nnpx shadcn@latest add https://platejs.org/r/editor-basic\n```\n\n### API Routes\n\nThese items provide server-side components or API route handlers necessary for certain features.\n\n```bash\n# Install AI-related API routes\nnpx shadcn@latest add https://platejs.org/r/ai-api\n\n# Install file upload API routes (e.g., for UploadThing)\nnpx shadcn@latest add https://platejs.org/r/media-uploadthing-api\n```\n\n### Documentation\n\nDocumentation files for various Plate features, guides, and API references can also be added to your local project. This is especially useful for keeping version-specific documentation alongside your code and for providing context to AI tools through MCP. Learn more about [setting up local documentation](/installation/docs).\n\n```bash\n# Add AI documentation\nnpx shadcn@latest add https://platejs.org/r/ai-docs\n\n# Add Plate Plugin documentation\nnpx shadcn@latest add https://platejs.org/r/plugin-docs\n```\n\nThese items consists of Markdown files, which can be integrated into your own documentation system or made available for local search and AI consumption.\n", + "content": "---\ntitle: Plate UI Installation\ndescription: How to set up Plate UI in your project.\n---\n\nThis guide details how to install Plate UI, the component layer for Plate. Choose the method that suits your project:\n\n* **CLI Installation (Recommended):** Quick setup using the shadcn CLI.\n* **Manual Installation:** For more control over the setup process.\n\n## CLI Installation (Recommended)\n\nThis is the fastest way to integrate Plate UI's core dependencies and base styles.\n\n\n\n### Install Plate UI\n\n\n If you are not using Next.js, refer to the official [shadcn/ui installation guides](https://ui.shadcn.com/docs/installation) for your specific framework before proceeding.\n\n\n```bash\nnpx shadcn@latest add @plate/plate-ui\n```\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n* **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n* **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n\n\n## Manual Installation\n\nIf you prefer a step-by-step approach or are not using the shadcn CLI, follow these steps:\n\n\n\n### Install Plate\n\n```bash\nnpm install platejs\n```\n\n\n When manually installing Plate, you'll need to add the specific packages for each plugin you want to use. For example, if you want to use the `AIMenu` component, you would need `@platejs/ai` and its dependencies. Check each component's documentation for its required packages, or use the CLI to automatically install the required packages.\n\n\n### Configure CSS Variables\n\nAdd the following CSS variables to your global stylesheet:\n\n```css title=\"app/globals.css\"\n:root {\n /* Base brand color for Plate UI components */\n --brand: oklch(0.623 0.214 259.815);\n}\n\n.dark {\n --brand: oklch(0.707 0.165 254.624);\n}\n```\n\n### Add Components\n\nWith Plate UI's core installed, you can now add individual Plate UI components to build your editor interface. For example, to add the `FixedToolbar` and a `MarkToolbarButton`:\n\n```bash\nnpx shadcn@latest add @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\nImport and use them in your editor:\n\n```tsx showLineNumbers title=\"components/editor.tsx\"\nimport { Plate } from \"platejs/react\";\nimport { FixedToolbar } from \"@/components/ui/fixed-toolbar\";\nimport { MarkToolbarButton } from \"@/components/ui/mark-toolbar-button\";\n// ... other imports\n\nexport function MyEditor() {\n // ... editor setup\n return (\n \n \n B\n {/* ... other toolbar buttons ... */}\n \n {/* ... Editor component ... */}\n \n );\n}\n```\n\nExplore the available [UI Components](/docs/components) and [Plugin Components](/docs/plugin-components) to customize your editor nodes (like paragraphs, headings, etc.) and build a feature-rich editing experience.\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n* **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n* **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n\n\n## Component Types\n\nWhen installing Plate UI components, you'll encounter different types of components with consistent naming patterns. Here's what they mean:\n\n### Feature Kits\n\nFeature kits are the easiest way to add complete functionality to your editor. They bundle all necessary plugin configurations, UI components (including node renderers and toolbar items), and their underlying npm dependencies for a specific feature.\n\n```bash\n# Install the complete AI feature suite, including configuration and UI\nnpx shadcn@latest add @plate/ai-kit\n\n# Install drag and drop functionality with all its parts\nnpx shadcn@latest add @plate/dnd-kit\n```\n\n\n When in doubt about what individual components you need, start with a feature kit. It's designed to provide a working feature out-of-the-box. You can always customize or remove parts later.\n\n\n### Components\n\n#### Node Components\n\nThese components are responsible for rendering specific types of content (elements or leaves) in your editor. If you need to create your own or customize existing ones, see the [Plugin Components guide](/docs/plugin-components).\n\n```bash\n# Install the component for rendering blockquotes\nnpx shadcn@latest add @plate/blockquote-node\n\n# Install the component for rendering code text\nnpx shadcn@latest add @plate/code-node\n```\n\n#### Toolbar Components\n\nToolbar components add interactive controls like buttons and dropdowns to your editor's toolbar.\n\n```bash\n# Add an alignment dropdown for your toolbar\nnpx shadcn@latest add @plate/align-toolbar-button\n\n# Add a toolbar button for AI features\nnpx shadcn@latest add @plate/ai-toolbar-button\n\n# Add a color picker dropdown for your toolbar\nnpx shadcn@latest add @plate/font-color-toolbar-button\n```\n\n#### Other Components\n\nFinally, Plate UI includes more advanced editor components as overlays, menus, block wrappers, etc.\n\n```bash\n# Install a menu for AI features\nnpx shadcn@latest add @plate/ai-menu\n\n# Install a draggable component for reordering blocks\nnpx shadcn@latest add @plate/block-draggable\n```\n\nThese components are typically included as part of their respective Feature Kits but can be installed individually for custom setups.\n\n### Editor Templates\n\nThese are pre-configured editor setups, often tailored for specific use cases or as a comprehensive starting point. You can explore all available [Editor Templates](/editors).\n\n```bash\n# Install an AI-enabled editor template\nnpx shadcn@latest add @plate/editor-ai\n\n# Install a basic editor template\nnpx shadcn@latest add @plate/editor-basic\n```\n\n### API Routes\n\nThese items provide server-side components or API route handlers necessary for certain features.\n\n```bash\n# Install AI-related API routes\nnpx shadcn@latest add @plate/ai-api\n\n# Install file upload API routes (e.g., for UploadThing)\nnpx shadcn@latest add @plate/media-uploadthing-api\n```\n\n### Documentation\n\nDocumentation files for various Plate features, guides, and API references can also be added to your local project. This is especially useful for keeping version-specific documentation alongside your code and for providing context to AI tools through MCP. Learn more about [setting up local documentation](/installation/docs).\n\n```bash\n# Add AI documentation\nnpx shadcn@latest add @plate/ai-docs\n\n# Add Plate Plugin documentation\nnpx shadcn@latest add @plate/plugin-docs\n```\n\nThese items consists of Markdown files, which can be integrated into your own documentation system or made available for local search and AI consumption.\n", "type": "registry:file", "target": "content/docs/plate/installation/plate-ui.mdx" } diff --git a/apps/www/public/r/installation-react-docs.json b/apps/www/public/r/installation-react-docs.json index c702edc0ab..bf3d279007 100644 --- a/apps/www/public/r/installation-react-docs.json +++ b/apps/www/public/r/installation-react-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/installation/react.mdx", - "content": "---\ntitle: React\ndescription: Install and configure Plate UI for React\n---\n\n\n Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation) (adapted for your framework, e.g., Vite) and [Plate UI](/docs/installation/plate-ui).\n\n\nThis guide walks you through incrementally building a Plate editor in your project.\n\n\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/editor\n```\n\nNext, create a basic editor in your main application file (e.g. `src/App.tsx`). This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"src/App.tsx\"\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function App() {\n const editor = usePlateEditor(); // Initializes the editor instance\n\n return (\n {/* Provides editor context */}\n {/* Styles the editor area */}\n \n \n \n );\n}\n```\n\n\n `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n\n\n\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add https://platejs.org/r/basic-nodes-kit https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button\n```\n\n\n The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n\n\nUpdate your `src/App.tsx` to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {2,4-8,15-16,18-31,35-36,41-45}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n {\n type: 'p',\n children: [\n { text: 'Hello! Try out the ' },\n { text: 'bold', bold: true },\n { text: ', ' },\n { text: 'italic', italic: true },\n { text: ', and ' },\n { text: 'underline', underline: true },\n { text: ' formatting.' },\n ],\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n value: initialValue, // Set initial content\n });\n\n return (\n \n \n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {5,7-9,18,21,23,26-33,50-53,61-65}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: initialValue,\n });\n\n return (\n \n \n {/* Element Toolbar Buttons */}\n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n {/* Mark Toolbar Buttons */}\n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n\n Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n ```bash\n npx shadcn@latest add https://platejs.org/r/editor-basic\n ```\n This handles much of the boilerplate for you.\n\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {55-58,64-66,76-82}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: () => {\n const savedValue = localStorage.getItem('installation-react-demo');\n return savedValue ? JSON.parse(savedValue) : initialValue;\n },\n });\n\n return (\n {\n localStorage.setItem('installation-react-demo', JSON.stringify(value));\n }}\n >\n \n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n B\n I\n U\n
\n editor.tf.setValue(initialValue)}\n >\n Reset\n \n \n \n \n \n \n );\n}\n```\n\n\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in your project.\n\nTo further enhance your editor:\n\n* **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more.\n* **Add Plugins:** Integrate features like [Tables](/docs/plugins/table), [Mentions](/docs/plugins/mention), [AI](/docs/plugins/ai), or [Markdown](/docs/plugins/markdown).\n* **Use Editor Blocks:** Quickly set up pre-configured editors:\n * Basic editor: `npx shadcn@latest add https://platejs.org/r/editor-basic`\n * AI-powered editor: `npx shadcn@latest add https://platejs.org/r/editor-ai`\n* **Learn More:**\n * [Editor Configuration](/docs/editor)\n * [Plugin Configuration](/docs/plugin)\n * [Plugin Components](/docs/plugin-components)\n\n\n", + "content": "---\ntitle: React\ndescription: Install and configure Plate UI for React\n---\n\n\n Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation) (adapted for your framework, e.g., Vite) and [Plate UI](/docs/installation/plate-ui).\n\n\nThis guide walks you through incrementally building a Plate editor in your project.\n\n\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add @plate/editor\n```\n\nNext, create a basic editor in your main application file (e.g. `src/App.tsx`). This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"src/App.tsx\"\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function App() {\n const editor = usePlateEditor(); // Initializes the editor instance\n\n return (\n {/* Provides editor context */}\n {/* Styles the editor area */}\n \n \n \n );\n}\n```\n\n\n `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n\n\n\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\n\n The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n\n\nUpdate your `src/App.tsx` to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {2,4-8,15-16,18-31,35-36,41-45}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n {\n type: 'p',\n children: [\n { text: 'Hello! Try out the ' },\n { text: 'bold', bold: true },\n { text: ', ' },\n { text: 'italic', italic: true },\n { text: ', and ' },\n { text: 'underline', underline: true },\n { text: ' formatting.' },\n ],\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n value: initialValue, // Set initial content\n });\n\n return (\n \n \n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {5,7-9,18,21,23,26-33,50-53,61-65}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: initialValue,\n });\n\n return (\n \n \n {/* Element Toolbar Buttons */}\n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n {/* Mark Toolbar Buttons */}\n B\n I\n U\n \n \n \n \n \n );\n}\n```\n\n\n\n\n Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n ```bash\n npx shadcn@latest add @plate/editor-basic\n ```\n This handles much of the boilerplate for you.\n\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {55-58,64-66,76-82}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n BlockquotePlugin,\n BoldPlugin,\n H1Plugin,\n H2Plugin,\n H3Plugin,\n ItalicPlugin,\n UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n Plate,\n usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n {\n children: [{ text: 'Title' }],\n type: 'h3',\n },\n {\n children: [{ text: 'This is a quote.' }],\n type: 'blockquote',\n },\n {\n children: [\n { text: 'With some ' },\n { bold: true, text: 'bold' },\n { text: ' text for emphasis!' },\n ],\n type: 'p',\n },\n];\n\nexport default function App() {\n const editor = usePlateEditor({\n plugins: [\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n H1Plugin.withComponent(H1Element),\n H2Plugin.withComponent(H2Element),\n H3Plugin.withComponent(H3Element),\n BlockquotePlugin.withComponent(BlockquoteElement),\n ],\n value: () => {\n const savedValue = localStorage.getItem('installation-react-demo');\n return savedValue ? JSON.parse(savedValue) : initialValue;\n },\n });\n\n return (\n {\n localStorage.setItem('installation-react-demo', JSON.stringify(value));\n }}\n >\n \n editor.tf.h1.toggle()}>H1\n editor.tf.h2.toggle()}>H2\n editor.tf.h3.toggle()}>H3\n editor.tf.blockquote.toggle()}>Quote\n B\n I\n U\n
\n editor.tf.setValue(initialValue)}\n >\n Reset\n \n \n \n \n \n \n );\n}\n```\n\n\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in your project.\n\nTo further enhance your editor:\n\n* **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more.\n* **Add Plugins:** Integrate features like [Tables](/docs/plugins/table), [Mentions](/docs/plugins/mention), [AI](/docs/plugins/ai), or [Markdown](/docs/plugins/markdown).\n* **Use Editor Blocks:** Quickly set up pre-configured editors:\n * Basic editor: `npx shadcn@latest add @plate/editor-basic`\n * AI-powered editor: `npx shadcn@latest add @plate/editor-ai`\n* **Learn More:**\n * [Editor Configuration](/docs/editor)\n * [Plugin Configuration](/docs/plugin)\n * [Plugin Components](/docs/plugin-components)\n\n\n", "type": "registry:file", "target": "content/docs/plate/installation/react.mdx" } diff --git a/apps/www/public/r/troubleshooting-docs.json b/apps/www/public/r/troubleshooting-docs.json index e80694214e..f89a6654db 100644 --- a/apps/www/public/r/troubleshooting-docs.json +++ b/apps/www/public/r/troubleshooting-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/(guides)/troubleshooting.mdx", - "content": "---\ntitle: Troubleshooting\ndescription: Solutions for common issues when working with Plate.\n---\n\n## Dependency Conflicts\n\nA common source of issues in projects using Plate is mismatched or conflicting versions of dependencies. This section outlines how to identify and resolve such conflicts.\n\n### Managing Plate Package Versions with `depset`\n\nThe recommended way to ensure all your ``@udecode/*`` packages (including Plate and its related plugins) are synchronized to a consistent and compatible set of versions is by using the [`depset`](https://npmjs.com/package/depset) command-line tool.\n\n**Why `depset`?**\n- It simplifies upgrading or aligning multiple packages within the ``@udecode`` scope.\n- It helps prevent issues caused by having some Plate packages on one version and others on a different, potentially incompatible, version.\n\n**Usage:**\n\nTo upgrade or align all packages in the ``@udecode`` scope to a specific target version (e.g., ``45.0.1``), run the following in your project root:\n```bash\nnpx depset@latest @udecode 45.0.1\n```\n\nTo upgrade all ``@udecode`` packages to the latest versions that are less than major version ``46`` (e.g., if ``45.x.y`` are the latest releases, it will pick those):\n```bash\nnpx depset@latest @udecode 45\n```\n\n- Replace ```` (e.g., ``45.0.1`` or ``45``) with your desired version specifier.\n- ``depset`` will update your ``package.json``.\n\n### Example: Multiple Plate Instances\n\n**Problem:** Unexpected behavior or \"hooks can only be called inside a component\" errors.\n\n**Root Cause:** Having incompatible versions of Plate packages in your project. This often means different ``platejs*`` packages or ``@platejs/core`` are at different versions that weren't designed to work together.\n\n**Diagnosis:** Check for multiple Plate package versions:\n\n```bash\n# npm\nnpm ls platejs @platejs/core\n\n# pnpm or yarn\npnpm why platejs\npnpm why @platejs/core\n```\n\n**Solution:**\nThe primary solution is to ensure all your ``@udecode/*`` packages are updated to their latest respective versions that are compatible and designed to work together. This prevents mismatches where one Plate package might be too old or too new for others in your project. Use the ``depset`` tool as described above.\n\n### Example: Multiple Slate Instances\n\n**Problem:** Editor features may not work correctly.\n\n**Root Cause:** Package managers sometimes install mismatched versions of Slate dependencies. For example, `pnpm` might install `slate` version 0.112.2 instead of the required 0.111.0.\n\n**Diagnosis:** Check for multiple Slate versions:\n\n```bash\n# npm\nnpm ls slate slate-react slate-dom\n\n# pnpm or yarn\npnpm why slate\npnpm why slate-react\npnpm why slate-dom\n```\n\n**Solution:** Try these solutions in the order they are listed:\n\n1. Remove `slate*` dependencies from your `package.json` if any. Plate is managing those.\n\n2. Use the ``depset`` tool as described above.\n\n2. Force consistent Slate dependency versions:\n\n```jsonc\n// package.json\n{\n \"resolutions\": {\n \"slate\": \"0.114.0\",\n \"slate-dom\": \"0.114.0\",\n \"slate-react\": \"0.114.2\"\n }\n}\n```\n\n", + "content": "---\ntitle: Troubleshooting\ndescription: Solutions for common issues when working with Plate.\n---\n\n## Dependency Conflicts\n\nA common source of issues in projects using Plate is mismatched or conflicting versions of dependencies. This section outlines how to identify and resolve such conflicts.\n\n### Managing Plate Package Versions with `depset`\n\nThe recommended way to ensure all your ``@platejs/*`` packages (including Plate and its related plugins) are synchronized to a consistent and compatible set of versions is by using the [`depset`](https://npmjs.com/package/depset) command-line tool.\n\n**Why `depset`?**\n- It simplifies upgrading or aligning multiple packages within the ``@platejs`` scope.\n- It helps prevent issues caused by having some Plate packages on one version and others on a different, potentially incompatible, version.\n\n**Usage:**\n\nTo upgrade or align all packages in the ``@platejs`` scope to a specific target version (e.g., ``52.0.1``), run the following in your project root:\n```bash\nnpx depset@latest @platejs 52.0.1 && npx depset@latest platejs 52.0.1\n```\n\nTo upgrade all ``@platejs`` packages to the latest versions that are less than major version ``46`` (e.g., if ``52.x.y`` are the latest releases, it will pick those):\n```bash\nnpx depset@latest @platejs 52 && npx depset@latest platejs 52 \n```\n\n- Replace ```` (e.g., ``52.0.1`` or ``52``) with your desired version specifier.\n- ``depset`` will update your ``package.json``.\n\n### Example: Multiple Plate Instances\n\n**Problem:** Unexpected behavior or \"hooks can only be called inside a component\" errors.\n\n**Root Cause:** Having incompatible versions of Plate packages in your project. This often means different ``platejs*`` packages or ``@platejs/core`` are at different versions that weren't designed to work together.\n\n**Diagnosis:** Check for multiple Plate package versions:\n\n```bash\n# npm\nnpm ls platejs @platejs/core\n\n# pnpm or yarn\npnpm why platejs\npnpm why @platejs/core\n```\n\n**Solution:**\nThe primary solution is to ensure all your ``@platejs/*`` packages are updated to their latest respective versions that are compatible and designed to work together. This prevents mismatches where one Plate package might be too old or too new for others in your project. Use the ``depset`` tool as described above.\n\n### Example: Multiple Slate Instances\n\n**Problem:** Editor features may not work correctly.\n\n**Root Cause:** Package managers sometimes install mismatched versions of Slate dependencies. For example, `pnpm` might install `slate` version 0.112.2 instead of the required 0.111.0.\n\n**Diagnosis:** Check for multiple Slate versions:\n\n```bash\n# npm\nnpm ls slate slate-react slate-dom\n\n# pnpm or yarn\npnpm why slate\npnpm why slate-react\npnpm why slate-dom\n```\n\n**Solution:** Try these solutions in the order they are listed:\n\n1. Remove `slate*` dependencies from your `package.json` if any. Plate is managing those.\n\n2. Use the ``depset`` tool as described above.\n\n2. Force consistent Slate dependency versions:\n\n```jsonc\n// package.json\n{\n \"resolutions\": {\n \"slate\": \"0.114.0\",\n \"slate-dom\": \"0.114.0\",\n \"slate-react\": \"0.114.2\"\n }\n}\n```\n\n", "type": "registry:file", "target": "content/docs/plate/(guides)/troubleshooting.mdx" } diff --git a/apps/www/public/r/typescript-docs.json b/apps/www/public/r/typescript-docs.json index c83e2c31ee..8f05f39112 100644 --- a/apps/www/public/r/typescript-docs.json +++ b/apps/www/public/r/typescript-docs.json @@ -7,7 +7,7 @@ "files": [ { "path": "../../docs/(guides)/typescript.mdx", - "content": "---\ntitle: TypeScript\ndescription: Configure TypeScript (tsconfig) for using Plate, including module resolution solutions.\n---\n\nPlate provides ESM packages, which require certain TypeScript (and bundler) configurations to ensure compatibility, especially when importing subpath modules like `platejs/react`. Below are several solutions and workarounds to make TypeScript happy.\n\n## Quick Summary\n\n1. **Recommended (Easiest):** Use TypeScript **5.0+** and set `\"moduleResolution\": \"bundler\"` in your `tsconfig.json`.\n2. **Alternate (Node resolution):** Keep `\"moduleResolution\": \"node\"` and map paths to `dist/react` (and potentially alias them in your bundler config).\n3. **Up-to-date Packages:** Use `depset` to upgrade Plate dependencies.\n\n\n## Recommended: `\"moduleResolution\": \"bundler\"`\n\nThe simplest approach for modern bundlers (Vite, Next.js 14, etc.) is to enable the new TypeScript \"bundler\" resolution mode. Example:\n\n```jsonc\n// tsconfig.json\n{\n \"compilerOptions\": {\n // ...\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n // ...\n }\n}\n```\n\nThis aligns TypeScript's resolution logic more closely with modern bundlers and ESM packages. Below is a working excerpt from [Plate template](https://github.com/udecode/plate-template):\n\n```jsonc\n{\n \"compilerOptions\": {\n \"strict\": false,\n \"strictNullChecks\": true,\n \"allowUnusedLabels\": false,\n \"allowUnreachableCode\": false,\n \"exactOptionalPropertyTypes\": false,\n \"noFallthroughCasesInSwitch\": true,\n \"noImplicitOverride\": true,\n \"noImplicitReturns\": false,\n \"noPropertyAccessFromIndexSignature\": false,\n \"noUncheckedIndexedAccess\": false,\n \"noUnusedLocals\": false,\n \"noUnusedParameters\": false,\n\n \"isolatedModules\": true,\n\n \"allowJs\": true,\n \"checkJs\": false,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"jsx\": \"preserve\",\n \"module\": \"esnext\",\n \"target\": \"es2022\",\n \"moduleResolution\": \"bundler\",\n \"moduleDetection\": \"force\",\n \"resolveJsonModule\": true,\n \"noEmit\": true,\n \"incremental\": true,\n \"sourceMap\": true,\n\n \"baseUrl\": \"src\",\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \".next/types/**/*.ts\",\n \"src/**/*.ts\",\n \"src/**/*.tsx\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n```\n\n- **`\"moduleResolution\": \"bundler\"`** was introduced in TypeScript 5.0.\n- If your TS version is older than 5.0, you **must** upgrade or stick to `\"moduleResolution\": \"node\"` plus manual path aliases.\n\n```jsonc\n// package.json\n{\n \"devDependencies\": {\n \"typescript\": \"^5.0.0\"\n }\n}\n```\n\nIf you see an error like `TS5023: Unknown compiler option 'moduleResolution'` (for `bundler`), it likely means your TypeScript version is below 5.0.\n\n## Workaround: `\"moduleResolution\": \"node\"` + Path Aliases\n\nIf upgrading your entire project to TS 5.0 or changing the resolution mode is not possible:\n\n1. Keep `\"moduleResolution\": \"node\"`.\n2. Map each Plate subpath import to its `dist/react` types in `tsconfig.json` using `paths`.\n3. Alias these paths in your bundler config.\n\n### Example `tsconfig.json`\n\n```jsonc\n{\n \"compilerOptions\": {\n \"moduleResolution\": \"node\",\n \"paths\": {\n \"platejs/react\": [\n \"./node_modules/platejs/dist/react/index.d.ts\"\n ],\n \"@platejs/core/react\": [\n \"./node_modules/@platejs/core/dist/react/index.d.ts\"\n ],\n \"@platejs/list/react\": [\n \"./node_modules/@platejs/list/dist/react/index.d.ts\"\n ]\n // ...repeat for all @platejs/*/react packages\n }\n }\n}\n```\n\n### Example `vite.config.ts`\n\n```ts\nimport { defineConfig } from 'vite';\nimport path from 'path';\n\nexport default defineConfig({\n resolve: {\n alias: {\n 'platejs/react': path.resolve(\n __dirname,\n 'node_modules/platejs/dist/react'\n ),\n '@platejs/core/react': path.resolve(\n __dirname,\n 'node_modules/@platejs/core/dist/react'\n ),\n '@platejs/list/react': path.resolve(\n __dirname,\n 'node_modules/@platejs/list/dist/react'\n ),\n\n // Non-/react base aliases:\n 'platejs': path.resolve(\n __dirname,\n 'node_modules/platejs'\n ),\n '@platejs/core': path.resolve(\n __dirname,\n 'node_modules/@platejs/core'\n ),\n '@platejs/list': path.resolve(\n __dirname,\n 'node_modules/@platejs/list'\n )\n }\n }\n});\n```\n\n**Note:**\n- You must do this for every `@platejs/*/react` import you use. \n- For testing/Jest, replicate these aliases via `moduleNameMapper` or similar.\n\n## Ensure Matching Plate Versions\n\nSay you're upgrading one package to `42.0.3`, double-check that all your `platejs*` packages are on the **latest version up to `42.0.3`** (one package could stay at `42.0.2` if it has no `42.0.3` release). Mixing versions often leads to mismatches.\n\nTo easily manage and synchronize your `platejs*` package versions, you can use the `depset` CLI. For example, to ensure all your `@udecode` scope packages are aligned to the latest compatible with version `42.x.y`:\n\n```bash\nnpx depset@latest @udecode 42\n```\n\nOr, for a specific version like `42.0.3` (this will set all packages in the scope to `42.0.3` if available, or the latest before it if not):\n\n```bash\nnpx depset@latest @udecode 42.0.3\n```\n\nThis helps prevent version conflicts by ensuring all related Plate packages are on compatible versions.\n\n## FAQ\n\n> I updated `moduleResolution` to `bundler` but it broke my older imports.\"\n\nIf your codebase has older TS usage or relies on `node` resolution, try the path alias approach or fully migrate to a TS 5+ / ESM environment.\n\n> \"I'm seeing `TS2305` about missing exports. Is that a resolution error or a real missing export?\" \n\nIt can be either:\n- If the entire package is \"not found,\" it's likely a resolution issue. \n- If it's specifically \"no exported member,\" double-check that you spelled the import correctly (no typos) and that your installed version actually has that export.\n\n> \"Which minimum TS version do I need for `moduleResolution: bundler`?\" \n\nTypeScript 5.0 or higher.\n\n> \"We switched to bundler resolution, but some older libraries in our project break.\" \n\nIf your older libraries aren't ESM-friendly, you might stick to `node` resolution and do manual path aliases. Some large codebases gradually upgrade or create separate build pipelines for legacy code.\n\n> \"We see the error in Jest but not in Vite.\" \n\nYou'll need to replicate your alias/resolution changes for Jest. For example:\n\n```js\n// jest.config.js\nmodule.exports = {\n // ...\n moduleNameMapper: {\n '^platejs/react$': '/node_modules/platejs/dist/react',\n '^@platejs/core/react$': '/node_modules/@platejs/core/dist/react',\n // ...\n }\n};\n```\n", + "content": "---\ntitle: TypeScript\ndescription: Configure TypeScript (tsconfig) for using Plate, including module resolution solutions.\n---\n\nPlate provides ESM packages, which require certain TypeScript (and bundler) configurations to ensure compatibility, especially when importing subpath modules like `platejs/react`. Below are several solutions and workarounds to make TypeScript happy.\n\n## Quick Summary\n\n1. **Recommended (Easiest):** Use TypeScript **5.0+** and set `\"moduleResolution\": \"bundler\"` in your `tsconfig.json`.\n2. **Alternate (Node resolution):** Keep `\"moduleResolution\": \"node\"` and map paths to `dist/react` (and potentially alias them in your bundler config).\n3. **Up-to-date Packages:** Use `depset` to upgrade Plate dependencies.\n\n\n## Recommended: `\"moduleResolution\": \"bundler\"`\n\nThe simplest approach for modern bundlers (Vite, Next.js 14, etc.) is to enable the new TypeScript \"bundler\" resolution mode. Example:\n\n```jsonc\n// tsconfig.json\n{\n \"compilerOptions\": {\n // ...\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n // ...\n }\n}\n```\n\nThis aligns TypeScript's resolution logic more closely with modern bundlers and ESM packages. Below is a working excerpt from [Plate template](https://github.com/udecode/plate-template):\n\n```jsonc\n{\n \"compilerOptions\": {\n \"strict\": false,\n \"strictNullChecks\": true,\n \"allowUnusedLabels\": false,\n \"allowUnreachableCode\": false,\n \"exactOptionalPropertyTypes\": false,\n \"noFallthroughCasesInSwitch\": true,\n \"noImplicitOverride\": true,\n \"noImplicitReturns\": false,\n \"noPropertyAccessFromIndexSignature\": false,\n \"noUncheckedIndexedAccess\": false,\n \"noUnusedLocals\": false,\n \"noUnusedParameters\": false,\n\n \"isolatedModules\": true,\n\n \"allowJs\": true,\n \"checkJs\": false,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"jsx\": \"preserve\",\n \"module\": \"esnext\",\n \"target\": \"es2022\",\n \"moduleResolution\": \"bundler\",\n \"moduleDetection\": \"force\",\n \"resolveJsonModule\": true,\n \"noEmit\": true,\n \"incremental\": true,\n \"sourceMap\": true,\n\n \"baseUrl\": \"src\",\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \".next/types/**/*.ts\",\n \"src/**/*.ts\",\n \"src/**/*.tsx\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n```\n\n- **`\"moduleResolution\": \"bundler\"`** was introduced in TypeScript 5.0.\n- If your TS version is older than 5.0, you **must** upgrade or stick to `\"moduleResolution\": \"node\"` plus manual path aliases.\n\n```jsonc\n// package.json\n{\n \"devDependencies\": {\n \"typescript\": \"^5.0.0\"\n }\n}\n```\n\nIf you see an error like `TS5023: Unknown compiler option 'moduleResolution'` (for `bundler`), it likely means your TypeScript version is below 5.0.\n\n## Workaround: `\"moduleResolution\": \"node\"` + Path Aliases\n\nIf upgrading your entire project to TS 5.0 or changing the resolution mode is not possible:\n\n1. Keep `\"moduleResolution\": \"node\"`.\n2. Map each Plate subpath import to its `dist/react` types in `tsconfig.json` using `paths`.\n3. Alias these paths in your bundler config.\n\n### Example `tsconfig.json`\n\n```jsonc\n{\n \"compilerOptions\": {\n \"moduleResolution\": \"node\",\n \"paths\": {\n \"platejs/react\": [\n \"./node_modules/platejs/dist/react/index.d.ts\"\n ],\n \"@platejs/core/react\": [\n \"./node_modules/@platejs/core/dist/react/index.d.ts\"\n ],\n \"@platejs/list/react\": [\n \"./node_modules/@platejs/list/dist/react/index.d.ts\"\n ]\n // ...repeat for all @platejs/*/react packages\n }\n }\n}\n```\n\n### Example `vite.config.ts`\n\n```ts\nimport { defineConfig } from 'vite';\nimport path from 'path';\n\nexport default defineConfig({\n resolve: {\n alias: {\n 'platejs/react': path.resolve(\n __dirname,\n 'node_modules/platejs/dist/react'\n ),\n '@platejs/core/react': path.resolve(\n __dirname,\n 'node_modules/@platejs/core/dist/react'\n ),\n '@platejs/list/react': path.resolve(\n __dirname,\n 'node_modules/@platejs/list/dist/react'\n ),\n\n // Non-/react base aliases:\n 'platejs': path.resolve(\n __dirname,\n 'node_modules/platejs'\n ),\n '@platejs/core': path.resolve(\n __dirname,\n 'node_modules/@platejs/core'\n ),\n '@platejs/list': path.resolve(\n __dirname,\n 'node_modules/@platejs/list'\n )\n }\n }\n});\n```\n\n**Note:**\n- You must do this for every `@platejs/*/react` import you use. \n- For testing/Jest, replicate these aliases via `moduleNameMapper` or similar.\n\n## Ensure Matching Plate Versions\n\nSay you're upgrading one package to `52.0.1`, double-check that all your `platejs*` packages are on the **latest version up to `52.0.1`** (one package could stay at `52.0.0` if it has no `52.0.1` release). Mixing versions often leads to mismatches.\n\nTo easily manage and synchronize your `platejs*` package versions, you can use the `depset` CLI. For example, to ensure all your `@platejs/*` scope packages are aligned to the latest compatible with version `52.x.y`:\n\n```bash\nnpx depset@latest @platejs 52 && npx depset@latest platejs 52\n```\n\nOr, for a specific version like `52.0.1` (this will set all packages in the scope to `52.0.1` if available, or the latest before it if not):\n\n```bash\nnpx depset@latest @platejs 52.0.1 && npx depset@latest platejs 52.0.1\n```\n\nThis helps prevent version conflicts by ensuring all related Plate packages are on compatible versions.\n\n## FAQ\n\n> I updated `moduleResolution` to `bundler` but it broke my older imports.\"\n\nIf your codebase has older TS usage or relies on `node` resolution, try the path alias approach or fully migrate to a TS 5+ / ESM environment.\n\n> \"I'm seeing `TS2305` about missing exports. Is that a resolution error or a real missing export?\" \n\nIt can be either:\n- If the entire package is \"not found,\" it's likely a resolution issue. \n- If it's specifically \"no exported member,\" double-check that you spelled the import correctly (no typos) and that your installed version actually has that export.\n\n> \"Which minimum TS version do I need for `moduleResolution: bundler`?\" \n\nTypeScript 5.0 or higher.\n\n> \"We switched to bundler resolution, but some older libraries in our project break.\" \n\nIf your older libraries aren't ESM-friendly, you might stick to `node` resolution and do manual path aliases. Some large codebases gradually upgrade or create separate build pipelines for legacy code.\n\n> \"We see the error in Jest but not in Vite.\" \n\nYou'll need to replicate your alias/resolution changes for Jest. For example:\n\n```js\n// jest.config.js\nmodule.exports = {\n // ...\n moduleNameMapper: {\n '^platejs/react$': '/node_modules/platejs/dist/react',\n '^@platejs/core/react$': '/node_modules/@platejs/core/dist/react',\n // ...\n }\n};\n```\n", "type": "registry:file", "target": "content/docs/plate/(guides)/typescript.mdx" } diff --git a/apps/www/src/registry/ui/emoji-toolbar-button.tsx b/apps/www/src/registry/ui/emoji-toolbar-button.tsx index 95066cd240..20b8817bea 100644 --- a/apps/www/src/registry/ui/emoji-toolbar-button.tsx +++ b/apps/www/src/registry/ui/emoji-toolbar-button.tsx @@ -1,4 +1,5 @@ 'use client'; +/* eslint-disable react-hooks/refs */ import * as React from 'react'; @@ -340,7 +341,6 @@ function EmojiPickerContent({ return (
- {/* eslint-disable-next-line react-hooks/refs */}
{isSearching ? SearchList() : EmojiList()}
diff --git a/docs/components/changelog.mdx b/docs/components/changelog.mdx index f19fd28738..4b24f33965 100644 --- a/docs/components/changelog.mdx +++ b/docs/components/changelog.mdx @@ -296,7 +296,7 @@ Plugins: - `draggable`: - Fix dnd in Firefox - `media-placeholder-element`: refactor to use `use-upload-file` hook instead of `uploadthing` - - Migration: `npx shadcn@latest add https://platejs.org/r/api-uploadthing` + - Migration: `npx shadcn@latest add @plate/api-uploadthing` ### May 6 #22.3 diff --git a/docs/installation/docs.mdx b/docs/installation/docs.mdx index d526295235..8916cd7b96 100644 --- a/docs/installation/docs.mdx +++ b/docs/installation/docs.mdx @@ -93,7 +93,7 @@ Create a new file called `components.json` in your docs directory with this cont Now, fetch the Plate documentation files and necessary MDX components. ```bash -npx shadcn@latest add https://platejs.org/r/fumadocs +npx shadcn@latest add @plate/fumadocs ``` @@ -128,7 +128,7 @@ If you just want the documentation files without setting up a full site, you can ```bash # Run from your project root (wherever you want the docs) -npx shadcn@latest add https://platejs.org/r/docs +npx shadcn@latest add @plate/docs ``` This will: @@ -150,14 +150,13 @@ Enable AI tools to work with your local documentation by adding the Plate server ```json { "mcpServers": { - "plate": { - "description": "Plate editors, plugins, components and documentation", - "type": "stdio", + "shadcn": { + "description": "Shadcn and Plate MCP", "command": "npx", - "args": ["-y", "shadcn@canary", "registry:mcp"], - "env": { - "REGISTRY_URL": "https://platejs.org/r/registry.json" - } + "args": [ + "shadcn@latest", + "mcp" + ] } } } diff --git a/docs/installation/mcp.mdx b/docs/installation/mcp.mdx index ce79ebba14..ceeb28d84a 100644 --- a/docs/installation/mcp.mdx +++ b/docs/installation/mcp.mdx @@ -56,7 +56,7 @@ Follow these steps to set up MCP with Plate: Run this command to initialize your project with the basic editor template: ```bash -npx shadcn@latest add https://platejs.org/r/editor-basic +npx shadcn@latest add @plate/editor-basic ``` ### Step 2: Add MCP config diff --git a/docs/installation/next.mdx b/docs/installation/next.mdx index ec20538c25..d8551b4d27 100644 --- a/docs/installation/next.mdx +++ b/docs/installation/next.mdx @@ -16,7 +16,7 @@ This guide walks you through incrementally building a Plate editor in your Next. Start by adding the core [Editor](/docs/components/editor) component to your project: ```bash -npx shadcn@latest add https://platejs.org/r/editor +npx shadcn@latest add @plate/editor ``` Next, create a basic editor page. This example sets up a simple editor within an `EditorContainer`. @@ -52,7 +52,7 @@ export default function MyEditorPage() { Enhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components: ```bash -npx shadcn@latest add https://platejs.org/r/basic-nodes-kit https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button +npx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button ``` @@ -212,7 +212,7 @@ export default function MyEditorPage() { For a quicker start with common plugins and components pre-configured, use the `editor-basic` block: ```bash - npx shadcn@latest add https://platejs.org/r/editor-basic + npx shadcn@latest add @plate/editor-basic ``` This handles much of the boilerplate for you. @@ -326,8 +326,8 @@ To further enhance your editor: * **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more. * **Add Plugins:** Integrate features like [Tables](/docs/table), [Mentions](/docs/mention), [AI](/docs/ai), or [Markdown](/docs/markdown). * **Use Editor Blocks:** Quickly set up pre-configured editors: - * Basic editor: `npx shadcn@latest add https://platejs.org/r/editor-basic` - * AI-powered editor: `npx shadcn@latest add https://platejs.org/r/editor-ai` + * Basic editor: `npx shadcn@latest add @plate/editor-basic` + * AI-powered editor: `npx shadcn@latest add @plate/editor-ai` * **Learn More:** * [Editor Configuration](/docs/editor) * [Plugin Configuration](/docs/plugin) diff --git a/docs/installation/plate-ui.mdx b/docs/installation/plate-ui.mdx index eec9dc3546..7cf6389ab6 100644 --- a/docs/installation/plate-ui.mdx +++ b/docs/installation/plate-ui.mdx @@ -21,7 +21,7 @@ This is the fastest way to integrate Plate UI's core dependencies and base style ```bash -npx shadcn@latest add https://platejs.org/r/plate-ui +npx shadcn@latest add @plate/plate-ui ``` ### Basic Usage @@ -69,7 +69,7 @@ Add the following CSS variables to your global stylesheet: With Plate UI's core installed, you can now add individual Plate UI components to build your editor interface. For example, to add the `FixedToolbar` and a `MarkToolbarButton`: ```bash -npx shadcn@latest add https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button +npx shadcn@latest add @plate/fixed-toolbar @plate/mark-toolbar-button ``` Import and use them in your editor: @@ -115,10 +115,10 @@ Feature kits are the easiest way to add complete functionality to your editor. T ```bash # Install the complete AI feature suite, including configuration and UI -npx shadcn@latest add https://platejs.org/r/ai-kit +npx shadcn@latest add @plate/ai-kit # Install drag and drop functionality with all its parts -npx shadcn@latest add https://platejs.org/r/dnd-kit +npx shadcn@latest add @plate/dnd-kit ``` @@ -133,10 +133,10 @@ These components are responsible for rendering specific types of content (elemen ```bash # Install the component for rendering blockquotes -npx shadcn@latest add https://platejs.org/r/blockquote-node +npx shadcn@latest add @plate/blockquote-node # Install the component for rendering code text -npx shadcn@latest add https://platejs.org/r/code-node +npx shadcn@latest add @plate/code-node ``` #### Toolbar Components @@ -145,13 +145,13 @@ Toolbar components add interactive controls like buttons and dropdowns to your e ```bash # Add an alignment dropdown for your toolbar -npx shadcn@latest add https://platejs.org/r/align-toolbar-button +npx shadcn@latest add @plate/align-toolbar-button # Add a toolbar button for AI features -npx shadcn@latest add https://platejs.org/r/ai-toolbar-button +npx shadcn@latest add @plate/ai-toolbar-button # Add a color picker dropdown for your toolbar -npx shadcn@latest add https://platejs.org/r/font-color-toolbar-button +npx shadcn@latest add @plate/font-color-toolbar-button ``` #### Other Components @@ -160,10 +160,10 @@ Finally, Plate UI includes more advanced editor components as overlays, menus, b ```bash # Install a menu for AI features -npx shadcn@latest add https://platejs.org/r/ai-menu +npx shadcn@latest add @plate/ai-menu # Install a draggable component for reordering blocks -npx shadcn@latest add https://platejs.org/r/block-draggable +npx shadcn@latest add @plate/block-draggable ``` These components are typically included as part of their respective Feature Kits but can be installed individually for custom setups. @@ -174,10 +174,10 @@ These are pre-configured editor setups, often tailored for specific use cases or ```bash # Install an AI-enabled editor template -npx shadcn@latest add https://platejs.org/r/editor-ai +npx shadcn@latest add @plate/editor-ai # Install a basic editor template -npx shadcn@latest add https://platejs.org/r/editor-basic +npx shadcn@latest add @plate/editor-basic ``` ### API Routes @@ -186,10 +186,10 @@ These items provide server-side components or API route handlers necessary for c ```bash # Install AI-related API routes -npx shadcn@latest add https://platejs.org/r/ai-api +npx shadcn@latest add @plate/ai-api # Install file upload API routes (e.g., for UploadThing) -npx shadcn@latest add https://platejs.org/r/media-uploadthing-api +npx shadcn@latest add @plate/media-uploadthing-api ``` ### Documentation @@ -198,10 +198,10 @@ Documentation files for various Plate features, guides, and API references can a ```bash # Add AI documentation -npx shadcn@latest add https://platejs.org/r/ai-docs +npx shadcn@latest add @plate/ai-docs # Add Plate Plugin documentation -npx shadcn@latest add https://platejs.org/r/plugin-docs +npx shadcn@latest add @plate/plugin-docs ``` These items consists of Markdown files, which can be integrated into your own documentation system or made available for local search and AI consumption. diff --git a/docs/installation/react.mdx b/docs/installation/react.mdx index ff19287fbf..73ad599ced 100644 --- a/docs/installation/react.mdx +++ b/docs/installation/react.mdx @@ -16,7 +16,7 @@ This guide walks you through incrementally building a Plate editor in your proje Start by adding the core [Editor](/docs/components/editor) component to your project: ```bash -npx shadcn@latest add https://platejs.org/r/editor +npx shadcn@latest add @plate/editor ``` Next, create a basic editor in your main application file (e.g. `src/App.tsx`). This example sets up a simple editor within an `EditorContainer`. @@ -50,7 +50,7 @@ export default function App() { Enhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/components/fixed-toolbar) and [MarkToolbarButton](/docs/components/mark-toolbar-button) components: ```bash -npx shadcn@latest add https://platejs.org/r/basic-nodes-kit https://platejs.org/r/fixed-toolbar https://platejs.org/r/mark-toolbar-button +npx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button ``` @@ -206,7 +206,7 @@ export default function App() { For a quicker start with common plugins and components pre-configured, use the `editor-basic` block: ```bash - npx shadcn@latest add https://platejs.org/r/editor-basic + npx shadcn@latest add @plate/editor-basic ``` This handles much of the boilerplate for you. @@ -318,8 +318,8 @@ To further enhance your editor: * **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/components), and more. * **Add Plugins:** Integrate features like [Tables](/docs/plugins/table), [Mentions](/docs/plugins/mention), [AI](/docs/plugins/ai), or [Markdown](/docs/plugins/markdown). * **Use Editor Blocks:** Quickly set up pre-configured editors: - * Basic editor: `npx shadcn@latest add https://platejs.org/r/editor-basic` - * AI-powered editor: `npx shadcn@latest add https://platejs.org/r/editor-ai` + * Basic editor: `npx shadcn@latest add @plate/editor-basic` + * AI-powered editor: `npx shadcn@latest add @plate/editor-ai` * **Learn More:** * [Editor Configuration](/docs/editor) * [Plugin Configuration](/docs/plugin) diff --git a/package.json b/package.json index 5431533e01..b370b5b856 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,8 @@ "brl": "yarn g:brl", "build": "yarn g:build", "build:apps": "turbo --filter \"./apps/www\" build", - "build:templates": "turbo --filter \"./templates/**\" build", "build:tw": "yarn workspace www build:tw", "build:watch": "ROARR_LOG=true turbowatch ./tooling/config/turbowatch.config.ts | roarr", - "check": "yarn lint && yarn eslint", - "cli:basic": "sh ./tooling/scripts/pre-basic.sh && npx shadcn@latest add http://localhost:3000/rd/editor-basic -o -c templates/plate-template && sh ./tooling/scripts/post-basic.sh", - "cli:basic:dev": "sh ./tooling/scripts/pre-basic.sh && npx shadcn@latest add http://localhost:3000/rd/paragraph-node -c ../../templates/plate-template && sh ./tooling/scripts/post-basic.sh", - "cli:sync": "cd templates/plate-playground-template && npx shadcn@latest add http://localhost:3000/rd/editor-ai -o", - "cli:test": "cd templates/plate-template && npx shadcn@latest add http://localhost:3000/rd/editor-ai -o", "deps:check": "npx npm-check-updates@latest --configFileName config/ncurc.yml --workspaces --root --mergeConfig", "deps:update": "npx npm-check-updates@latest --configFileName config/ncurc.yml -u --workspaces --root --mergeConfig", "dev": "turbo --filter=www dev", @@ -47,8 +41,8 @@ "g:typecheck": "turbo --filter \"./packages/**\" typecheck", "g:typecheck:all": "turbo typecheck", "gen:package": "yarn plop --plopfile tooling/scripts/plop/plopfile.cjs package", - "postinstall": "patch-package --patch-dir tooling/patches", - "lint": "biome check .", + "postinstall": "bunx @udecode/ruler@latest apply && patch-package --patch-dir tooling/patches", + "lint": "biome check . && eslint", "lint:fix": "biome check . --fix", "nuke:node_modules": "rimraf '**/node_modules'", "p:brl": "sh tooling/scripts/brl.sh", @@ -65,13 +59,12 @@ "rd": "yarn workspace www rd", "release": "yarn build && yarn changeset publish", "shadcn:build": "yarn workspace www shadcn:build", - "templates:basic": "sh ./tooling/scripts/pre-basic.sh && npx shadcn@latest add http://localhost:3000/rd/editor-basic -o -c templates/plate-template && sh ./tooling/scripts/post-basic.sh", - "templates:ai": "cd templates/plate-playground-template && npx shadcn@latest add http://localhost:3000/rd/editor-ai -o", + "templates:check": "cd templates/plate-template && bun lint && bun typecheck && cd ../plate-playground-template && bun lint && bun typecheck", "templates:push": "./tooling/scripts/push-templates.sh \"templates/*\"", - "templates:update": "pnpm templates:update:basic && pnpm templates:update:ai", - "templates:update:basic": "./tooling/scripts/update-template.sh basic", - "templates:update:ai": "./tooling/scripts/update-template.sh ai", - "templates:test": "cd templates/plate-template && npx shadcn@latest add http://localhost:3000/rd/editor-ai -o && pnpm typecheck", + "templates:basic": "./tooling/scripts/update-template.sh basic", + "templates:ai": "./tooling/scripts/update-template.sh ai", + "templates:update": "yarn templates:update:basic && yarn templates:update:ai", + "templates:test": "cd templates/plate-template && npx shadcn@latest add http://localhost:3000/rd/editor-ai -o && bun lint && bun typecheck", "test": "bun test", "test:coverage": "bun test --coverage", "test:watch": "bun test --watch", @@ -109,7 +102,6 @@ "@types/react-dom": "19.2.3", "@types/validator": "13.15.1", "@typescript-eslint/parser": "^8.47.0", - "@udecode/ruler": "0.4.0", "app-root-path": "3.1.0", "autoprefixer": "10.4.21", "babel-plugin-react-compiler": "1.0.0", diff --git a/templates/plate-playground-template/.claude/AGENTS.md b/templates/plate-playground-template/.claude/AGENTS.md new file mode 100644 index 0000000000..f04fdd905a --- /dev/null +++ b/templates/plate-playground-template/.claude/AGENTS.md @@ -0,0 +1,13 @@ +- In all interactions and commit messages, be extremely concise and sacrifice grammar for the sake of concision. + +## PR Comments + +- When tagging Claude in GitHub issues, use '@claude' + +## GitHub + +- Your primary method for interacting with GitHub should be the GitHub CLI. + +## Plans + +- At the end of each plan, give me a list of unresolved questions to answer, if any. Make the questions extremely concise. Sacrifice grammar for the sake of concision. diff --git a/templates/plate-playground-template/.claude/prompt.json b/templates/plate-playground-template/.claude/prompt.json new file mode 100644 index 0000000000..f4765924ae --- /dev/null +++ b/templates/plate-playground-template/.claude/prompt.json @@ -0,0 +1,55 @@ +{ + "beforeStart": [ + { + "tag": "MANDATORY-FIRST-RESPONSE", + "header": "🚨 STOP - YOUR FIRST TOOL CALL MUST BE TodoWrite", + "instructions": [ + "DO NOT analyze the task yet. DO NOT read files. DO NOT edit anything. DO NOT load skills yet.", + "YOUR FIRST ACTION: Call TodoWrite with the todo below, marking it as in_progress", + "Check if the todo's condition applies - if NO, mark completed immediately and proceed with normal work", + "If condition applies: work through all steps → mark completed → proceed with normal work", + "If you skip TodoWrite or jump straight to file operations, you have FAILED to follow instructions" + ], + "todos": [ + "Skill analysis (SKIP if message contains 'quick'): (1) Check if rationalizing with thoughts like 'simple question', 'just checking files', 'don't need formal skill', 'overkill', 'might be relevant' - if YES to ANY, stop rationalizing; (2) List ALL available skills from context; (3) For EACH skill: Read its description and check for 'always load first' or foundational patterns - mark these ✓ IMMEDIATELY. Then ask 'Does this task involve [skill topic]?' If YES or MIGHT or MAYBE = mark ✓. Only mark ✗ if DEFINITELY not related; (4) Use Skill tool to load ALL marked ✓ IN ONE PARALLEL CALL - do NOT load one skill then wait to see if it tells you to load others. Load ALL potentially relevant skills upfront; (5) Output '[Skills checked: X available, Y loaded: name1, name2, ...]'. CRITICAL: 'Might be relevant' = MUST load. 'Could apply' = MUST load. '1% chance' = MUST load. Do NOT depend on one skill to tell you to load another." + ] + } + ], + "beforeComplete": [ + { + "tag": "VERIFICATION-CHECKLIST", + "header": "Before claiming work is complete, fixed, or passing - NO completion claims without FRESH verification evidence:", + "instructions": [ + "Create TodoWrite with ALL todos below (each has its own condition)", + "For EACH todo: Check if its condition applies - if NO, mark completed immediately and skip to next", + "If condition applies: mark in_progress → complete the check → mark completed", + "Work through every todo even if some don't apply (conditions are per-todo, not global)", + "We manually start session with `bun logs && bun typecheck:watch` as Background Bash Shell - read output when checking errors", + "Run `bun typecheck` only when Background Bash Shells are not visible/running", + "NEVER make git commits unless user explicitly asked", + "NEVER run `bun dev` or `bun run build` unless explicitly asked" + ], + "todos": [ + "TypeScript check (ONLY if updated ts files): Verify no `any` used (pause and ask user if `any` seems required)", + "Typecheck (ONLY if updated ts files): Read typecheck:watch output OR run `bun typecheck` - verify passes", + "Lint: Run `bun lint:fix` - verify passes" + ] + } + ], + "afterCompact": [ + { + "tag": "POST-COMPACT-RECOVERY", + "header": "🚨 CONTEXT WIPED - MANDATORY SKILL RELOAD REQUIRED", + "instructions": [ + "STOP. Context compaction has DELETED all previously loaded skills. You have FORGOTTEN everything.", + "DO NOT proceed with any task until you complete skill reloading below", + "DO NOT assume you remember any skills - they are ALL gone from memory", + "YOUR IMMEDIATE ACTION: Complete the mandatory reload checklist below", + "Skipping this = GUARANTEED FAILURE because you lost all behavioral patterns" + ], + "todos": [ + "Skill reload after compaction: (1) Check TodoWrite to identify what task you were working on; (2) List ALL available skills from context; (3) For EACH skill: Read its description and check for 'always load first' or foundational patterns - mark these ✓ IMMEDIATELY. Then ask 'Does this apply to my current task?' If YES or MIGHT or MAYBE = mark ✓. Only mark ✗ if DEFINITELY not related; (4) Use Skill tool to load ALL marked ✓ IN ONE PARALLEL CALL (REQUIRED - they were wiped) - do NOT load one skill then wait to see if it tells you to load others. Load ALL potentially relevant skills upfront; (5) ONLY after reloading skills, resume the task. CRITICAL: All previously loaded skills are GONE and MUST be reloaded. 'Might apply' = MUST load." + ] + } + ] +} diff --git a/templates/plate-playground-template/.claude/ruler.toml b/templates/plate-playground-template/.claude/ruler.toml new file mode 100644 index 0000000000..db1fcd26d7 --- /dev/null +++ b/templates/plate-playground-template/.claude/ruler.toml @@ -0,0 +1,125 @@ +# Ruler Configuration for Informed Medical +# This project uses Ruler to manage AI agent instructions +# Source: .ruler/ directory | Generated: root AGENTS.md, CLAUDE.md, etc. + +# Default agents to apply when --agents flag is not specified +default_agents = ["claude", "codex", "cursor"] + +# --- Fork Configuration --- + +root_folder = ".claude" + +[rules] +merge_strategy = "cursor" + +[backup] +enabled = false + +# --- Global Configuration --- + +# Skills support (manages .ruler/skills/ → .claude/skills/ propagation) +[skills] +enabled = true +generate_from_rules = true + +# Automatic .gitignore management +[gitignore] +enabled = true + +# MCP (Model Context Protocol) server configuration +[mcp] +enabled = true +merge_strategy = "merge" + +# --- MCP Server Definitions --- +[mcp_servers.registries] +command = "npx" +args = ["-y", "shadcn@latest", "mcp"] + +# --- Agent-Specific Configuration --- + +# Claude Code (primary agent) +[agents.claude] +enabled = true +output_path = "CLAUDE.md" + +# Cursor (secondary agent) +[agents.cursor] +enabled = true +# Uses AGENTS.md by default + +# OpenAI Codex CLI +[agents.codex] +enabled = true +output_path_config = ".codex/config.toml" +# Uses AGENTS.md by default + +# Disable other agents +[agents.copilot] +enabled = false + +[agents.aider] +enabled = false + +[agents.windsurf] +enabled = false + +[agents.cline] +enabled = false + +[agents.crush] +enabled = false + +[agents.amp] +enabled = false + +[agents.amazonqcli] +enabled = false + +[agents.firebase] +enabled = false + +[agents.gemini-cli] +enabled = false + +[agents.jules] +enabled = false + +[agents.junie] +enabled = false + +[agents.kilocode] +enabled = false + +[agents.opencode] +enabled = false + +[agents.openhands] +enabled = false + +[agents.goose] +enabled = false + +[agents.qwen] +enabled = false + +[agents.roo] +enabled = false + +[agents.zed] +enabled = false + +[agents.trae] +enabled = false + +[agents.warp] +enabled = false + +[agents.kiro] +enabled = false + +[agents.firebender] +enabled = false + +[agents.augmentcode] +enabled = false diff --git a/templates/plate-playground-template/.claude/rules/1-app-design-document.mdc b/templates/plate-playground-template/.claude/rules/1-app-design-document.mdc new file mode 100644 index 0000000000..3dca9096ca --- /dev/null +++ b/templates/plate-playground-template/.claude/rules/1-app-design-document.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/templates/plate-playground-template/.claude/rules/2-tech-stack.mdc b/templates/plate-playground-template/.claude/rules/2-tech-stack.mdc new file mode 100644 index 0000000000..3dca9096ca --- /dev/null +++ b/templates/plate-playground-template/.claude/rules/2-tech-stack.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/templates/plate-playground-template/.claude/rules/3-project-status.mdc b/templates/plate-playground-template/.claude/rules/3-project-status.mdc new file mode 100644 index 0000000000..3dca9096ca --- /dev/null +++ b/templates/plate-playground-template/.claude/rules/3-project-status.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/templates/plate-playground-template/.claude/rules/components.mdc b/templates/plate-playground-template/.claude/rules/components.mdc new file mode 100644 index 0000000000..1585bcba8d --- /dev/null +++ b/templates/plate-playground-template/.claude/rules/components.mdc @@ -0,0 +1,1308 @@ +--- +description: React component architecture for creating composable, accessible components with data attributes. Use when creating/updating composable components, not for higher-level feature/page components. +alwaysApply: false +--- + + +# Accessibility + +URL: /accessibility + + +title: Accessibility +description: Building components that are usable by everyone, including users with disabilities who rely on assistive technologies. + + +Accessibility (a11y) is not an optional feature—it's a fundamental requirement for modern web components. Every component must be usable by everyone, including people with visual, motor, auditory, or cognitive disabilities. + +This guide is a non-exhaustive list of accessibility principles and patterns that you should follow when building components. It's not a comprehensive guide, but it should give you a sense of the types of issues you should be aware of. + +If you use a linter with strong accessibility rules like [Ultracite](https://www.ultracite.ai), these types of issues will likely be caught automatically, but it's still important to understand the principles. + +## Core Principles + +1. **Semantic HTML First** - Use native elements (` +// Output: + +// With asChild: Merges props + +// Output: +``` + +## How It Works + +Uses `React.cloneElement` to clone the child and merge props (including event handlers) from both parent and child components. The enhanced child is returned with combined functionality. + +## Key Benefits + +1. **Semantic HTML** - Use the most appropriate element (links for navigation, buttons for actions) +2. **Clean DOM Structure** - Eliminates wrapper elements and "wrapper hell" +3. **Design System Integration** - Works seamlessly with existing component libraries +4. **Component Composition** - Compose multiple behaviors onto a single element + +## Common Use Cases + +- **Custom Triggers** - Replace default triggers with custom components or links +- **Accessible Navigation** - Maintain semantic navigation elements +- **Form Integration** - Integrate with form libraries while preserving functionality + +## Best Practices + +1. **Maintain Accessibility** - Ensure child elements have proper semantics and ARIA attributes +2. **Document Support** - Use JSDoc to document the `asChild` prop in your component interfaces +3. **Test Forwarding** - Verify props are properly forwarded to child components +4. **Handle Edge Cases** - Consider conditional rendering and dynamic children + +## Common Pitfalls + +1. **Not Spreading Props** - Child components must spread `...props` to receive merged behavior +2. **Multiple Children** - `asChild` expects exactly one child element, not multiple +3. **Fragment Children** - Fragments are not valid, use actual HTML elements + +# Composition + +URL: /composition + + +title: Composition +description: The foundation of building modern UI components. + + +Composition, or composability, is the foundation of building modern UI components. It is one of the most powerful techniques for creating flexible, reusable components that can handle complex requirements without sacrificing API clarity. + +Instead of cramming all functionality into a single component with dozens of props, composition distributes responsibility across multiple cooperating components. + +Fernando gave a great talk about this at React Universe Conf 2025, where he shared his approach to rebuilding Slack's Message Composer as a composable component. + +
; +``` + +### 3. Trigger Component + +The Trigger component is the element that opens the accordion when activated. It is responsible for: + +- Rendering as a button by default (can be customized with `asChild`) +- Handling click events to open the accordion +- Managing focus when accordion closes +- Providing proper ARIA attributes + +Let's add this component to our Accordion component. + +```tsx title="@/components/ui/accordion.tsx" +export type AccordionTriggerProps = React.ComponentProps<'button'> & { + asChild?: boolean; +}; + +export const Trigger = ({ asChild, ...props }: AccordionTriggerProps) => ( + + {({ open, setOpen }) => + + + + +``` + +## Understanding `as` + +The `as` prop allows you to override the default element type of a component. Instead of being locked into a specific HTML element, you can adapt the component to render as any valid HTML tag or even another React component. + +```tsx +Content // Renders as default (div) +Content // Renders as
+Content // Renders as
+ ); +}; +``` + +## Controlled State + +Controlled state is when the component's state is managed by the parent component. Rather than keeping track of the state internally, we delegate this responsibility to the parent component. + +Let's rework the `Stepper` component to be controlled by the parent component: + +```tsx title="stepper.tsx" +type StepperProps = { + value: number; + setValue: (value: number) => void; +}; + +export const Stepper = ({ value, setValue }: StepperProps) => ( +
+

{value}

+ +
+); +``` + +## Merging states + +The best components support both controlled and uncontrolled state. This allows the component to be used in a variety of scenarios, and to be easily customized. + +[Radix UI](https://www.radix-ui.com/) maintain an internal utility for merging controllable and uncontrolled state called [`use-controllable-state`](https://github.com/radix-ui/primitives/tree/main/packages/react/use-controllable-state). While not intended for public use, registries like [Kibo UI](https://www.kibo-ui.com) have implemented this utility to build their own Radix-like components. + +Let's install the hook: + +```package-install +npm install @radix-ui/react-use-controllable-state +``` + +This lightweight hook gives you the same state management patterns used internally by Radix UI's component library, ensuring your components behave consistently with industry standards. + +The hook accepts three main parameters and returns a tuple with the current value and setter. Let's use it to merge the controlled and uncontrolled state of the `Stepper` component: + +```tsx title="stepper.tsx" +import { useControllableState } from '@radix-ui/react-use-controllable-state'; + +type StepperProps = { + value: number; + defaultValue: number; + onValueChange: (value: number) => void; +}; + +export const Stepper = ({ value: controlledValue, defaultValue, onValueChange }: StepperProps) => { + const [value, setValue] = useControllableState({ + prop: controlledValue, // The controlled value prop + defaultProp: defaultValue, // Default value for uncontrolled mode + onChange: onValueChange, // Called when value changes + }); + + return ( +
+

{value}

+ +
+ ); +}; +``` + +# Types + +URL: /types + + +title: Types +description: Extending the browser's native HTML elements for maximum customization. + + +When building reusable components, proper typing is essential for creating flexible, customizable, and type-safe interfaces. By following established patterns for component types, you can ensure your components are both powerful and easy to use. + +## Single Element Wrapping + +Each exported component should ideally wrap a single HTML or JSX element. This principle is fundamental to creating composable, customizable components. + +When a component wraps multiple elements, it becomes difficult to customize specific parts without prop drilling or complex APIs. Consider this anti-pattern: + +```tsx title="@/components/ui/card.tsx" +const Card = ({ title, description, footer, ...props }) => ( +
+
+

{title}

+

{description}

+
+
{footer}
+
+); +``` + +As we discussed in [Composition](/docs/composition), this approach creates several problems: + +- You can't customize the header styling without adding more props +- You can't control the HTML elements used for title and description +- You're forced into a specific DOM structure + +Instead, each layer should be its own component. This allows you to customize each layer independently, and to control the exact HTML elements used for the title and description. + +The benefits of this approach are: + +- **Maximum customization** - Users can style and modify each layer independently +- **No prop drilling** - Props go directly to the element that needs them +- **Semantic HTML** - Users can see and control the exact DOM structure +- **Better accessibility** - Direct control over ARIA attributes and semantic elements +- **Simpler mental model** - One component = one element + +## Extending HTML Attributes + +Every component should extend the native HTML attributes of the element it wraps. This ensures users have full control over the underlying HTML element. + +### Basic Pattern + +```tsx +export type CardRootProps = React.ComponentProps<'div'> & { + // Add your custom props here + variant?: 'default' | 'outlined'; +}; + +export const CardRoot = ({ variant = 'default', ...props }: CardRootProps) =>
; +``` + +### Common HTML Attribute Types + +React provides type definitions for all HTML elements. Use the appropriate one for your component: + +```tsx +// For div elements +type DivProps = React.ComponentProps<'div'>; + +// For button elements +type ButtonProps = React.ComponentProps<'button'>; + +// For input elements +type InputProps = React.ComponentProps<'input'>; + +// For form elements +type FormProps = React.ComponentProps<'form'>; + +// For anchor elements +type LinkProps = React.ComponentProps<'a'>; +``` + +### Handling Different Element Types + +When a component can render as different elements, use generics or union types: + +```tsx +// Using discriminated unions +export type ButtonProps = + | (React.ComponentProps<'button'> & { asChild?: false }) + | (React.ComponentProps<'div'> & { asChild: true }); + +// Or with a polymorphic approach +export type PolymorphicProps = { + as?: T; +} & React.ComponentPropsWithoutRef; +``` + +### Extending custom components + +If you're extending an existing component, you can use the `ComponentProps` type to get the props of the component. + +```tsx title="@/components/ui/share-button.tsx" +import type { ComponentProps } from 'react'; + +export type ShareButtonProps = ComponentProps<'button'>; + +export const ShareButton = (props: ShareButtonProps) => +
+ +``` + +# React Compiler + +## Context + +This project uses React Compiler, which automatically optimizes your React code through automatic memoization at build time. Manual memoization with `useMemo`, `useCallback`, and `React.memo` is rarely needed and often introduces unnecessary complexity. + +## Core Principle + +**Write clean, idiomatic React code. Let the compiler optimize it.** + +React Compiler automatically applies optimal memoization based on data flow analysis. It can even optimize cases that manual memoization cannot handle, such as memoizing values after conditional returns or within complex control flow. + +## Requirements + +### DO NOT Use Manual Memoization + +- **NEVER** wrap components with `React.memo` unless you have a specific, documented reason +- **NEVER** use `useMemo` for performance optimization - the compiler handles this +- **NEVER** use `useCallback` for performance optimization - the compiler handles this +- **NEVER** create inline functions and then wrap them in `useCallback` - this is redundant + +### When Manual Memoization IS Acceptable + +Manual memoization should only be used as an **escape hatch** for precise control in specific scenarios: + +1. **Effect Dependencies**: When a memoized value is used as a dependency in `useEffect` to prevent unnecessary effect re-runs +2. **External Library Integration**: When passing callbacks to non-React libraries that don't handle reference changes well +3. **Precise Control**: When you have profiled and verified that the compiler's automatic memoization is insufficient for a specific hotspot + +**CRITICAL**: If you use manual memoization, you MUST document why with a comment explaining the specific reason. + +## Examples + +### Component Memoization + + +```tsx +// ✅ Good - Let the compiler optimize +function ExpensiveComponent({ data, onClick }) { + const processedData = expensiveProcessing(data); + +const handleClick = (item) => { +onClick(item.id); +}; + +return ( + +
+{processedData.map(item => ( + handleClick(item)} /> +))} +
+); +} + +```` +The compiler automatically memoizes components and values, ensuring optimal re-rendering without manual intervention. +
+ + +```tsx +// ❌ Avoid - Unnecessary manual memoization +const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) { + const processedData = useMemo(() => { + return expensiveProcessing(data); + }, [data]); + + const handleClick = useCallback((item) => { + onClick(item.id); + }, [onClick]); + + return ( +
+ {processedData.map(item => ( + handleClick(item)} /> + ))} +
+ ); +}); +```` + +This manual memoization is redundant with React Compiler and adds unnecessary complexity. +
+ +### Event Handlers + + +```tsx +// ✅ Good - Simple event handler +function TodoList({ todos, onToggle }) { + const handleToggle = (id) => { + onToggle(id); + }; + +return ( + +
    +{todos.map(todo => ( + handleToggle(todo.id)} +/> +))} +
+); +} + +```` +The compiler optimizes this correctly without `useCallback`. +
+ + +```tsx +// ❌ Avoid - Unnecessary useCallback +function TodoList({ todos, onToggle }) { + const handleToggle = useCallback((id) => { + onToggle(id); + }, [onToggle]); + + return ( +
    + {todos.map(todo => ( + handleToggle(todo.id)} + /> + ))} +
+ ); +} +```` + +The `useCallback` is unnecessary and creates a subtle bug: the inline arrow function `() => handleToggle(todo.id)` creates a new function on every render anyway, breaking the memoization. +
+ +### Computed Values + + +```tsx +// ✅ Good - Direct computation +function UserProfile({ user }) { + const fullName = `${user.firstName} ${user.lastName}`; + const initials = `${user.firstName[0]}${user.lastName[0]}`.toUpperCase(); + +return ( + +
+

{fullName}

+ +
+); +} + +```` +The compiler automatically memoizes these computations when appropriate. +
+ + +```tsx +// ❌ Avoid - Unnecessary useMemo +function UserProfile({ user }) { + const fullName = useMemo( + () => `${user.firstName} ${user.lastName}`, + [user.firstName, user.lastName] + ); + + const initials = useMemo( + () => `${user.firstName[0]}${user.lastName[0]}`.toUpperCase(), + [user.firstName, user.lastName] + ); + + return ( +
+

{fullName}

+ +
+ ); +} +```` + +These `useMemo` calls are redundant and make the code harder to read. +
+ +### Conditional Memoization + + +```tsx +// ✅ Good - The compiler can memoize after early returns +function ThemeProvider({ children, theme }) { + if (!children) { + return null; + } + +// The compiler memoizes this even after the conditional return +const mergedTheme = mergeTheme(theme, defaultTheme); + +return ( + +{children} + +); +} + +```` +The compiler can memoize values after conditional returns, which is impossible with manual memoization. + + + +```tsx +// ❌ Avoid - Attempting manual memoization with early returns +function ThemeProvider({ children, theme }) { + const mergedTheme = useMemo( + () => mergeTheme(theme, defaultTheme), + [theme] + ); + + if (!children) { + return null; + } + + return ( + + {children} + + ); +} +```` + +This forces the expensive merge to run even when returning null, whereas the compiler optimizes this correctly. + + +### Acceptable Use Case: Effect Dependencies + + +```tsx +// ✅ Acceptable - useMemo for effect dependency control +function DataFetcher({ filters }) { + // Documented reason: Prevent effect from re-running when filters object + // reference changes but values remain the same + const stableFilters = useMemo( + () => ({ ...filters }), + [filters.category, filters.status, filters.dateRange] + ); + +useEffect(() => { +fetchData(stableFilters); +}, [stableFilters]); + +// ... +} + +```` +This is an acceptable escape hatch with a clear, documented reason. + + +### Acceptable Use Case: External Library Integration + + +```tsx +// ✅ Acceptable - useCallback for third-party library +function MapComponent({ markers, onMarkerClick }) { + // Documented reason: GoogleMaps library doesn't handle reference changes well + // and re-attaches all event listeners on every render + const handleMarkerClick = useCallback((marker) => { + onMarkerClick(marker.id); + }, [onMarkerClick]); + + useEffect(() => { + markers.forEach(marker => { + googleMapsApi.addClickListener(marker, handleMarkerClick); + }); + }, [markers, handleMarkerClick]); + + // ... +} +```` + +This is an acceptable escape hatch for external library integration. + + +## Effects + +### Principle + +- Treat Effects as an escape hatch for synchronizing React with external systems (DOM APIs, network, imperative libraries). If no external system is involved, keep the logic in render or event handlers. +- Rendering must stay pure. Event-driven work (buying, saving, submitting) belongs in the handler that caused it, not in an Effect. +- React Compiler assumes idiomatic React semantics. Avoid manual memoization tricks to influence dependency stability; rely on actual values and let the compiler hoist what it can. +- For mixing reactive and non-reactive logic, see [Separating Events from Effects](#separating-events-from-effects) for `useEffectEvent` patterns. + +### When to Add an Effect + +- Bridging React state to imperative APIs (media playback, map widgets, modals) where you must call imperative methods after paint. +- Subscribing to external stores or browser events; prefer `useSyncExternalStore` when possible so React manages resubscription for you. +- Performing work that must run because the component is visible (e.g., logging an analytics impression) with an understanding that it will run twice in development. + +### When _Not_ to Add an Effect + +- Deriving or filtering data for rendering. Compute it inline during render; React Compiler memoizes expensive branches. +- Resetting or coordinating state between components. Use keys, derive state from props, or lift state up instead of chaining Effects. +- Handling user interactions. Run imperative logic inside the event handler so React can batch updates and you avoid double execution. +- Preventing Strict Mode double-invocation. Never guard Effects with refs or flags to stop re-execution; fix the underlying cleanup instead. + +### Cleanup and Strict Mode + +- Always return a cleanup when the Effect allocates resources (connections, listeners, timers). React calls cleanup before re-running the Effect and on unmount. +- Expect every Effect to mount → cleanup → mount in development. Production runs once, but development ensures your Effect is resilient. +- Avoid side-stepping cleanup by storing mutable singletons in refs. This leaves background work running across navigations and breaks invariants. + +### React Compiler Considerations + +- Because the compiler stabilizes values for you, do not introduce `useMemo`/`useCallback` purely to satisfy Effect dependency linting. Refactor the Effect so it depends on real inputs. +- Let the dependency array express actual inputs. Suppressing ESLint warnings or omitting deps makes compiler output unreliable. +- Prefer custom hooks (`useData`, `useOnlineStatus`) to bundle complex Effect logic once. This keeps call sites simple and lets the compiler optimize the hook body. + + +```tsx +// ✅ Effect that syncs with an external API and cleans up +export function VideoPlayer({ src, isPlaying }: Props) { + const ref = useRef(null); + +useEffect(() => { +const node = ref.current; +if (!node) { +return; +} + + if (isPlaying) { + void node.play(); + } else { + node.pause(); + } + + return () => { + node.pause(); + }; + +}, [isPlaying]); + +return + + +```tsx +// ❌ Avoid - deriving state and triggering events inside an Effect +function ProductPage({ product }: Props) { + const [isInCart, setIsInCart] = useState(false); + + useEffect(() => { + if (product.isInCart) { + setIsInCart(true); // Derivation should happen during render + notifyAdd(product.id); // Event logic belongs in the click handler + } + }, [product]); + // ... +} +```` + +This mixes reactive synchronization with event-driven logic. Use `useEffectEvent` if you need to read current props without re-running the Effect. See [Separating Events from Effects](#separating-events-from-effects). + + + + - Use Effects only for external synchronization; keep render pure and events in handlers. + - Always implement cleanup so mount → cleanup → mount cycles in development behave identically to a single mount in production. + - Do not fight dependency linting with manual memoization; rely on actual inputs and let React Compiler optimize the rest. + - Prefer purpose-built hooks (`useSyncExternalStore`, custom hooks) for shared subscription or fetching logic. + + +## Separating Events from Effects + +### Principle + +Event handlers and Effects serve different purposes in React: + +- **Event handlers**: Run in response to specific user interactions. Non-reactive logic. +- **Effects**: Run when synchronization with external systems is needed. Reactive to dependencies. +- **Effect Events** (`useEffectEvent`): Extract non-reactive logic from Effects when you need both behaviors. + +**CRITICAL DUAL-USE PATTERN**: When a function is needed in BOTH event handlers/props AND useEffect: + +1. Create a **regular function** for props/event handlers +2. Wrap it in **`useEffectEvent`** for use inside Effects only +3. Never pass Effect Events as props - they can ONLY be called inside Effects + +### Choosing Between Event Handlers and Effects + +Ask: "Does this run because of a specific interaction, or because the component needs to stay synchronized?" + + +```tsx +// ✅ Good - Combines both patterns appropriately +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + +// Event handler: runs on user click +function handleSendClick() { +sendMessage(message); +} + +// Effect: keeps connection synchronized with roomId +useEffect(() => { +const connection = createConnection(serverUrl, roomId); +connection.connect(); +return () => connection.disconnect(); +}, [roomId]); + +return ( +<> + +

Welcome to the {roomId} room!

+ setMessage(e.target.value)} /> + + +); +} + +```` +
+ +### Reactive Values and Reactive Logic + +**Reactive values** (props, state, derived values) can change on re-render. **Reactive logic** responds to these changes: + +- **Event handlers are NOT reactive**: Read reactive values but don't re-run when they change +- **Effects ARE reactive**: Must declare reactive values as dependencies and re-run when they change + +### Extracting Non-Reactive Logic with useEffectEvent + +Use `useEffectEvent` to mix reactive and non-reactive logic: + + +```tsx +// ❌ Without useEffectEvent - Reconnects on theme change +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); // Makes Effect reactive to theme + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, theme]); // ❌ Unnecessary reconnection + + return

Welcome to the {roomId} room!

; +} + +// ✅ With useEffectEvent - Only reconnects on roomId change +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); // Reads current theme, not reactive + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', onConnected); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ Only roomId + + return

Welcome to the {roomId} room!

; +} +```` + +
+ +### Reading Latest Props and State with Effect Events + +Effect Events always see latest values without causing re-runs. Pass reactive values as arguments for clarity: + + +```tsx +// ✅ Common patterns with useEffectEvent + +// 1. Page visit logging - Only logs when url changes, not cart +function Page({ url }) { +const { itemCount } = useCart(); + +const onVisit = useEffectEvent((visitedUrl) => { +logVisit(visitedUrl, itemCount); // Reads latest itemCount +}); + +useEffect(() => { +onVisit(url); // Pass url as argument for clarity +}, [url]); +} + +// 2. Event listener with current state +function useEventListener(emitter, eventName, handler) { +const stableHandler = useEffectEvent(handler); + +useEffect(() => { +emitter.on(eventName, stableHandler); +return () => emitter.off(eventName, stableHandler); +}, [emitter, eventName]); // Handler always sees current state +} + +// 3. Async operations - Stable trigger value, latest context +function AnalyticsPage({ url }) { +const { itemCount } = useCart(); + +const onVisit = useEffectEvent((visitedUrl) => { +setTimeout(() => { +logVisit(visitedUrl, itemCount); // visitedUrl: stable, itemCount: latest +}, 5000); +}); + +useEffect(() => { +onVisit(url); +}, [url]); +} + +```` + + +### Critical Rules and Limitations + +**Never suppress the dependency linter** - use `useEffectEvent` instead: + + +```tsx +// ❌ Suppressing linter creates stale closure +function Component() { + const [canMove, setCanMove] = useState(true); + + function handleMove(e) { + if (canMove) { // Always sees initial value! + setPosition({ x: e.clientX, y: e.clientY }); + } + } + + useEffect(() => { + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +} + +// ✅ Use Effect Event instead +function Component() { + const [canMove, setCanMove] = useState(true); + + const onMove = useEffectEvent((e) => { + if (canMove) { // Always sees current value + setPosition({ x: e.clientX, y: e.clientY }); + } + }); + + useEffect(() => { + window.addEventListener('pointermove', onMove); + return () => window.removeEventListener('pointermove', onMove); + }, []); +} +```` + + + +**Effect Event limitations**: + +**CRITICAL**: Effect Events can ONLY be called from inside Effects (or other Effect Events). They: + +1. **CANNOT be returned from hooks** - ESLint will error if you try to return them +2. **CANNOT be passed to other components or hooks** - ESLint will error if you try to pass them +3. **CANNOT be passed as props** - Props must receive regular functions, not Effect Events +4. **Must be declared locally** where the Effect uses them +5. **Keep close to the Effect** using them + +### Pattern: Function Used in Both Props AND Effects + +**When a function is needed in BOTH event handlers/props AND useEffect:** + +1. Create a regular function for props/event handler use +2. Wrap it in `useEffectEvent` for Effect use only +3. Use the regular function in props, Effect Event in the Effect + + +```tsx +// ✅ CORRECT - Dual use: props and Effect +function Component({ editingMessage }) { + const store = useStore(); + +// Regular function for props/event handlers +const handleCancelEdit = () => { +store.set('editingMessage', null); +store.set('input', ''); +}; + +// Effect Event wrapper for Effect use +const onCancelEdit = useEffectEvent(handleCancelEdit); + +// Effect uses Effect Event version +useEffect(() => { +const handleKeyDown = (e: KeyboardEvent) => { +if (e.key === 'Escape' && editingMessage) { +onCancelEdit(); // Use Effect Event here +} +}; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + +}, [editingMessage]); // No handleCancelEdit in deps! + +// Props use regular function +return ; +} + +```` + + + +```tsx +// ❌ WRONG - Passing Effect Event as prop +function Component() { + const handleClick = useEffectEvent(() => { + // ... + }); + + return
*/} - -

+

Not stored anywhere. Used only for current session requests.

diff --git a/templates/plate-playground-template/src/components/editor/transforms.ts b/templates/plate-playground-template/src/components/editor/transforms.ts index f4067116d2..4fc216be58 100644 --- a/templates/plate-playground-template/src/components/editor/transforms.ts +++ b/templates/plate-playground-template/src/components/editor/transforms.ts @@ -1,7 +1,5 @@ 'use client'; -import type { PlateEditor } from 'platejs/react'; - import { insertCallout } from '@platejs/callout'; import { insertCodeBlock, toggleCodeBlock } from '@platejs/code-block'; import { insertDate } from '@platejs/date'; @@ -19,12 +17,13 @@ import { SuggestionPlugin } from '@platejs/suggestion/react'; import { TablePlugin } from '@platejs/table/react'; import { insertToc } from '@platejs/toc'; import { + KEYS, type NodeEntry, type Path, - type TElement, - KEYS, PathApi, + type TElement, } from 'platejs'; +import type { PlateEditor } from 'platejs/react'; const ACTION_THREE_COLUMNS = 'action_three_columns'; @@ -187,7 +186,9 @@ export const setBlockType = ( const entries = editor.api.blocks({ mode: 'lowest' }); - entries.forEach((entry) => setEntry(entry)); + entries.forEach((entry) => { + setEntry(entry); + }); }); }; @@ -195,11 +196,11 @@ export const getBlockType = (block: TElement) => { if (block[KEYS.listType]) { if (block[KEYS.listType] === KEYS.ol) { return KEYS.ol; - } else if (block[KEYS.listType] === KEYS.listTodo) { + } + if (block[KEYS.listType] === KEYS.listTodo) { return KEYS.listTodo; - } else { - return KEYS.ul; } + return KEYS.ul; } return block.type; diff --git a/templates/plate-playground-template/src/components/editor/use-chat.ts b/templates/plate-playground-template/src/components/editor/use-chat.ts index d048a939e3..88ada7add2 100644 --- a/templates/plate-playground-template/src/components/editor/use-chat.ts +++ b/templates/plate-playground-template/src/components/editor/use-chat.ts @@ -1,16 +1,15 @@ 'use client'; -import * as React from 'react'; - import { type UseChatHelpers, useChat as useBaseChat } from '@ai-sdk/react'; import { faker } from '@faker-js/faker'; import { AIChatPlugin, aiCommentToRange } from '@platejs/ai/react'; import { getCommentKey, getTransientCommentKey } from '@platejs/comment'; import { deserializeMd } from '@platejs/markdown'; import { BlockSelectionPlugin } from '@platejs/selection/react'; -import { type UIMessage, DefaultChatTransport } from 'ai'; -import { type TNode, KEYS, nanoid, NodeApi, TextApi } from 'platejs'; +import { DefaultChatTransport, type UIMessage } from 'ai'; +import { KEYS, NodeApi, nanoid, TextApi, type TNode } from 'platejs'; import { type PlateEditor, useEditorRef, usePluginOption } from 'platejs/react'; +import * as React from 'react'; import { aiChatPlugin } from '@/components/editor/plugins/ai-kit'; @@ -54,7 +53,7 @@ export const useChat = () => { transport: new DefaultChatTransport({ api: options.api || '/api/ai/command', // Mock the API response. Remove it when you implement the route /api/ai/command - fetch: async (input, init) => { + fetch: (async (input, init) => { const bodyOptions = editor.getOptions(aiChatPlugin).chatOptions?.body; const initBody = JSON.parse(init?.body as string); @@ -109,7 +108,7 @@ export const useChat = () => { } return res; - }, + }) as typeof fetch, }), onData(data) { if (data.type === 'data-toolName') { @@ -118,7 +117,6 @@ export const useChat = () => { if (data.type === 'data-comment' && data.data) { if (data.data.status === 'finished') { - editor.getApi(AIChatPlugin).aiChat.hide(); editor.getApi(BlockSelectionPlugin).blockSelection.deselect(); return; @@ -228,17 +226,17 @@ const fakeStreamText = ({ return [ Array.from({ length: chunkCount }, () => ({ delay: faker.number.int({ max: 100, min: 30 }), - texts: faker.lorem.words({ max: 3, min: 1 }) + ' ', + texts: `${faker.lorem.words({ max: 3, min: 1 })} `, })), Array.from({ length: chunkCount + 2 }, () => ({ delay: faker.number.int({ max: 100, min: 30 }), - texts: faker.lorem.words({ max: 3, min: 1 }) + ' ', + texts: `${faker.lorem.words({ max: 3, min: 1 })} `, })), Array.from({ length: chunkCount + 4 }, () => ({ delay: faker.number.int({ max: 100, min: 30 }), - texts: faker.lorem.words({ max: 3, min: 1 }) + ' ', + texts: `${faker.lorem.words({ max: 3, min: 1 })} `, })), ]; })(); @@ -1489,7 +1487,7 @@ const createCommentChunks = (editor: PlateEditor) => { const indexes = Array.from(result).sort((a, b) => a - b); const chunks = indexes - .map((index) => { + .map((index, i) => { const block = blocks[index]; if (!block) { return []; @@ -1503,7 +1501,7 @@ const createCommentChunks = (editor: PlateEditor) => { return [ { delay: faker.number.int({ max: 500, min: 200 }), - texts: `{"id":"${nanoid()}","data":{"blockId":"${block.id}","comment":"${faker.lorem.sentence()}","content":"${content}"},"type":"data-comment"}`, + texts: `{"id":"${nanoid()}","data":{"comment":{"blockId":"${block.id}","comment":"${faker.lorem.sentence()}","content":"${content}"},"status":"${i === indexes.length - 1 ? 'finished' : 'streaming'}"},"type":"data-comment"}`, }, ]; }) diff --git a/templates/plate-playground-template/src/components/ui/ai-chat-editor.tsx b/templates/plate-playground-template/src/components/ui/ai-chat-editor.tsx index f04c609e30..7b10fa1301 100644 --- a/templates/plate-playground-template/src/components/ui/ai-chat-editor.tsx +++ b/templates/plate-playground-template/src/components/ui/ai-chat-editor.tsx @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; - import { useAIChatEditor } from '@platejs/ai/react'; import { usePlateEditor } from 'platejs/react'; +import * as React from 'react'; import { BaseEditorKit } from '@/components/editor/editor-base-kit'; @@ -20,5 +19,5 @@ export const AIChatEditor = React.memo(function AIChatEditor({ useAIChatEditor(aiEditor, content); - return ; + return ; }); diff --git a/templates/plate-playground-template/src/components/ui/ai-menu.tsx b/templates/plate-playground-template/src/components/ui/ai-menu.tsx index e0d2ebb330..aacb0ca157 100644 --- a/templates/plate-playground-template/src/components/ui/ai-menu.tsx +++ b/templates/plate-playground-template/src/components/ui/ai-menu.tsx @@ -1,7 +1,5 @@ 'use client'; -import * as React from 'react'; - import { AIChatPlugin, AIPlugin, @@ -30,21 +28,23 @@ import { X, } from 'lucide-react'; import { - type NodeEntry, - type SlateEditor, isHotkey, KEYS, NodeApi, + type NodeEntry, + type SlateEditor, TextApi, } from 'platejs'; import { + type PlateEditor, useEditorPlugin, + useEditorRef, useFocusedLast, useHotkeys, usePluginOption, } from 'platejs/react'; -import { type PlateEditor, useEditorRef } from 'platejs/react'; - +import * as React from 'react'; +import { commentPlugin } from '@/components/editor/plugins/comment-kit'; import { Button } from '@/components/ui/button'; import { Command, @@ -58,7 +58,6 @@ import { PopoverContent, } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; -import { commentPlugin } from '@/components/editor/plugins/comment-kit'; import { AIChatEditor } from './ai-chat-editor'; @@ -111,7 +110,6 @@ export function AIMenu() { }; useEditorChat({ - chat, onOpenBlockSelection: (blocks: NodeEntry[]) => { show(editor.api.toDOMNode(blocks.at(-1)![0])!); }, @@ -176,26 +174,26 @@ export function AIMenu() { if (toolName === 'edit' && mode === 'chat' && isLoading) return null; return ( - + { e.preventDefault(); api.aiChat.hide(); }} - align="center" side="bottom" + style={{ + width: anchorElement?.offsetWidth, + }} > {mode === 'chat' && isSelecting && @@ -203,18 +201,19 @@ export function AIMenu() { toolName === 'generate' && } {isLoading ? ( -
+
{messages.length > 1 ? 'Editing...' : 'Thinking...'}
) : ( { if (isHotkey('backspace')(e) && input.length === 0) { e.preventDefault(); @@ -228,8 +227,7 @@ export function AIMenu() { }} onValueChange={setInput} placeholder="Ask AI anything..." - data-plate-focus - autoFocus + value={input} /> )} @@ -333,7 +331,7 @@ Start writing a new paragraph AFTER ONLY ONE SENTENCE` label: 'Discard', shortcut: 'Escape', value: 'discard', - onSelect: ({ editor, input }) => { + onSelect: ({ editor }) => { editor.getTransforms(AIPlugin).ai.undo(); editor.getApi(AIChatPlugin).aiChat.hide(); }, @@ -478,7 +476,7 @@ Start writing a new paragraph AFTER ONLY ONE SENTENCE` icon: , label: 'Try again', value: 'tryAgain', - onSelect: ({ editor, input }) => { + onSelect: ({ editor }) => { void editor.getApi(AIChatPlugin).aiChat.reload(); }, }, @@ -590,20 +588,20 @@ export const AIMenuItems = ({ return ( <> {menuGroups.map((group, index) => ( - + {group.items.map((menuItem) => ( { menuItem.onSelect?.({ aiEditor, - editor: editor, + editor, input, }); setInput(''); }} + value={menuItem.value} > {menuItem.icon} {menuItem.label} @@ -661,16 +659,16 @@ export function AILoadingBar() { return (
{status === 'submitted' ? 'Thinking...' : 'Writing...'} diff --git a/templates/plate-playground-template/src/components/ui/ai-node.tsx b/templates/plate-playground-template/src/components/ui/ai-node.tsx index d244f92dc8..7fc6ba16cb 100644 --- a/templates/plate-playground-template/src/components/ui/ai-node.tsx +++ b/templates/plate-playground-template/src/components/ui/ai-node.tsx @@ -2,10 +2,10 @@ import { AIChatPlugin } from '@platejs/ai/react'; import { - type PlateElementProps, - type PlateTextProps, PlateElement, + type PlateElementProps, PlateText, + type PlateTextProps, usePluginOption, } from 'platejs/react'; diff --git a/templates/plate-playground-template/src/components/ui/ai-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/ai-toolbar-button.tsx index f9ddaf61d0..d187f88b4c 100644 --- a/templates/plate-playground-template/src/components/ui/ai-toolbar-button.tsx +++ b/templates/plate-playground-template/src/components/ui/ai-toolbar-button.tsx @@ -1,9 +1,8 @@ 'use client'; -import * as React from 'react'; - import { AIChatPlugin } from '@platejs/ai/react'; import { useEditorPlugin } from 'platejs/react'; +import type * as React from 'react'; import { ToolbarButton } from './toolbar'; diff --git a/templates/plate-playground-template/src/components/ui/alert-dialog.tsx b/templates/plate-playground-template/src/components/ui/alert-dialog.tsx index 0863e40d92..ad34ff1d2a 100644 --- a/templates/plate-playground-template/src/components/ui/alert-dialog.tsx +++ b/templates/plate-playground-template/src/components/ui/alert-dialog.tsx @@ -1,15 +1,14 @@ -"use client" +'use client'; -import * as React from "react" -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" - -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; +import type * as React from 'react'; +import { buttonVariants } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; function AlertDialog({ ...props }: React.ComponentProps) { - return + return ; } function AlertDialogTrigger({ @@ -17,7 +16,7 @@ function AlertDialogTrigger({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogPortal({ @@ -25,7 +24,7 @@ function AlertDialogPortal({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogOverlay({ @@ -34,14 +33,14 @@ function AlertDialogOverlay({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogContent({ @@ -52,44 +51,44 @@ function AlertDialogContent({ - ) + ); } function AlertDialogHeader({ className, ...props -}: React.ComponentProps<"div">) { +}: React.ComponentProps<'div'>) { return (
- ) + ); } function AlertDialogFooter({ className, ...props -}: React.ComponentProps<"div">) { +}: React.ComponentProps<'div'>) { return (
- ) + ); } function AlertDialogTitle({ @@ -98,11 +97,11 @@ function AlertDialogTitle({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogDescription({ @@ -111,11 +110,11 @@ function AlertDialogDescription({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogAction({ @@ -127,7 +126,7 @@ function AlertDialogAction({ className={cn(buttonVariants(), className)} {...props} /> - ) + ); } function AlertDialogCancel({ @@ -136,10 +135,10 @@ function AlertDialogCancel({ }: React.ComponentProps) { return ( - ) + ); } export { @@ -154,4 +153,4 @@ export { AlertDialogDescription, AlertDialogAction, AlertDialogCancel, -} +}; diff --git a/templates/plate-playground-template/src/components/ui/align-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/align-toolbar-button.tsx index 5fcf646064..c9a47a95a1 100644 --- a/templates/plate-playground-template/src/components/ui/align-toolbar-button.tsx +++ b/templates/plate-playground-template/src/components/ui/align-toolbar-button.tsx @@ -1,11 +1,8 @@ 'use client'; -import * as React from 'react'; - import type { Alignment } from '@platejs/basic-styles'; -import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; - import { TextAlignPlugin } from '@platejs/basic-styles/react'; +import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; import { AlignCenterIcon, AlignJustifyIcon, @@ -13,6 +10,7 @@ import { AlignRightIcon, } from 'lucide-react'; import { useEditorPlugin, useSelectionFragmentProp } from 'platejs/react'; +import * as React from 'react'; import { DropdownMenu, @@ -56,25 +54,25 @@ export function AlignToolbarButton(props: DropdownMenuProps) { items.find((item) => item.value === value)?.icon ?? AlignLeftIcon; return ( - + - + - + { tf.textAlign.setNodes(value as Alignment); editor.tf.focus(); }} + value={value} > {items.map(({ icon: Icon, value: itemValue }) => ( diff --git a/templates/plate-playground-template/src/components/ui/avatar.tsx b/templates/plate-playground-template/src/components/ui/avatar.tsx index 71e428b4ca..54f7bfb3d4 100644 --- a/templates/plate-playground-template/src/components/ui/avatar.tsx +++ b/templates/plate-playground-template/src/components/ui/avatar.tsx @@ -1,9 +1,9 @@ -"use client" +'use client'; -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import type * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; function Avatar({ className, @@ -11,14 +11,14 @@ function Avatar({ }: React.ComponentProps) { return ( - ) + ); } function AvatarImage({ @@ -27,11 +27,11 @@ function AvatarImage({ }: React.ComponentProps) { return ( - ) + ); } function AvatarFallback({ @@ -40,14 +40,14 @@ function AvatarFallback({ }: React.ComponentProps) { return ( - ) + ); } -export { Avatar, AvatarImage, AvatarFallback } +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/templates/plate-playground-template/src/components/ui/block-context-menu.tsx b/templates/plate-playground-template/src/components/ui/block-context-menu.tsx index c96aa6ec3d..5523fd1bb5 100644 --- a/templates/plate-playground-template/src/components/ui/block-context-menu.tsx +++ b/templates/plate-playground-template/src/components/ui/block-context-menu.tsx @@ -1,7 +1,5 @@ 'use client'; -import * as React from 'react'; - import { AIChatPlugin } from '@platejs/ai/react'; import { BLOCK_CONTEXT_MENU_ID, @@ -10,6 +8,7 @@ import { } from '@platejs/selection/react'; import { KEYS } from 'platejs'; import { useEditorPlugin, usePlateState, usePluginOption } from 'platejs/react'; +import * as React from 'react'; import { ContextMenu, @@ -66,12 +65,12 @@ export function BlockContextMenu({ children }: { children: React.ReactNode }) { return ( { if (!open) { api.blockMenu.hide(); } }} - modal={false} > { if (!_open_ && isCommenting && draftCommentNode) { editor.tf.unsetNodes(getDraftCommentKey(), { @@ -191,6 +187,7 @@ const BlockCommentContent = ({ } setOpen(_open_); }} + open={open} >
{children}
{anchorElement && ( @@ -202,50 +199,46 @@ const BlockCommentContent = ({ )} e.preventDefault()} onOpenAutoFocus={(e) => e.preventDefault()} - align="center" side="bottom" > {isCommenting ? ( - ) : ( - - {noneActive ? ( - sortedMergedData.map((item, index) => - isResolvedSuggestion(item) ? ( - - ) : ( - - ) - ) + ) : noneActive ? ( + sortedMergedData.map((item, index) => + isResolvedSuggestion(item) ? ( + ) : ( - - {activeSuggestion && ( - - )} - - {activeDiscussion && ( - - )} - + + ) + ) + ) : ( + <> + {activeSuggestion && ( + + )} + + {activeDiscussion && ( + )} - + )} @@ -253,10 +246,10 @@ const BlockCommentContent = ({
@@ -294,12 +287,12 @@ function BlockComment({
{discussion.comments.map((comment, index) => ( diff --git a/templates/plate-playground-template/src/components/ui/block-draggable.tsx b/templates/plate-playground-template/src/components/ui/block-draggable.tsx index db24b7600c..20561d1ec7 100644 --- a/templates/plate-playground-template/src/components/ui/block-draggable.tsx +++ b/templates/plate-playground-template/src/components/ui/block-draggable.tsx @@ -1,22 +1,21 @@ 'use client'; -import * as React from 'react'; - import { DndPlugin, useDraggable, useDropLine } from '@platejs/dnd'; import { expandListItemsWithChildren } from '@platejs/list'; import { BlockSelectionPlugin } from '@platejs/selection/react'; import { GripVertical } from 'lucide-react'; -import { type TElement, getPluginByType, isType, KEYS } from 'platejs'; +import { getPluginByType, isType, KEYS, type TElement } from 'platejs'; import { + MemoizedChildren, type PlateEditor, type PlateElementProps, type RenderNodeWrapper, - MemoizedChildren, useEditorRef, useElement, usePluginOption, + useSelected, } from 'platejs/react'; -import { useSelected } from 'platejs/react'; +import * as React from 'react'; import { Button } from '@/components/ui/button'; import { @@ -147,11 +146,11 @@ function Draggable(props: PlateElementProps) { )} > @@ -306,12 +271,11 @@ export const useResolveSuggestion = ( } if (!nodes && lineBreakId !== id) { - return setOption('uniquePathMap', new Map(map).set(id, blockPath)); + setOption('uniquePathMap', new Map(map).set(id, blockPath)); } - - return; + } else { + setOption('uniquePathMap', new Map(map).set(id, blockPath)); } - setOption('uniquePathMap', new Map(map).set(id, blockPath)); }); const resolvedSuggestion: ResolvedSuggestion[] = React.useMemo(() => { @@ -328,15 +292,19 @@ export const useResolveSuggestion = ( (data) => data.type === 'update' ); - if (!includeUpdate) return api.suggestion.nodeId(node); + if (!includeUpdate) { + return api.suggestion.nodeId(node) ?? []; + } return dataList .filter((data) => data.type === 'update') .map((d) => d.id); } if (ElementApi.isElement(node)) { - return api.suggestion.nodeId(node); + return api.suggestion.nodeId(node) ?? []; } + + return []; }) .filter(Boolean) ); @@ -362,9 +330,9 @@ export const useResolveSuggestion = ( ]; // move line break to the end - entries.sort(([, path1], [, path2]) => { - return PathApi.isChild(path1, path2) ? -1 : 1; - }); + entries.sort(([, path1], [, path2]) => + PathApi.isChild(path1, path2) ? -1 : 1 + ); let newText = ''; let text = ''; @@ -377,35 +345,35 @@ export const useResolveSuggestion = ( const dataList = api.suggestion.dataList(node); dataList.forEach((data) => { - if (data.id !== id) return; - - switch (data.type) { - case 'insert': { - newText += node.text; - - break; - } - case 'remove': { - text += node.text; - - break; + if (data.id === id) { + switch (data.type) { + case 'insert': { + newText += node.text; + + break; + } + case 'remove': { + text += node.text; + + break; + } + case 'update': { + properties = { + ...properties, + ...data.properties, + }; + + newProperties = { + ...newProperties, + ...data.newProperties, + }; + + newText += node.text; + + break; + } + // No default } - case 'update': { - properties = { - ...properties, - ...data.properties, - }; - - newProperties = { - ...newProperties, - ...data.newProperties, - }; - - newText += node.text; - - break; - } - // No default } }); } else { @@ -413,15 +381,16 @@ export const useResolveSuggestion = ( ? node.suggestion : undefined; - if (lineBreakData?.id !== keyId2SuggestionId(id)) return; - if (lineBreakData.type === 'insert') { - newText += lineBreakData.isLineBreak - ? BLOCK_SUGGESTION - : BLOCK_SUGGESTION + TYPE_TEXT_MAP[node.type](node); - } else if (lineBreakData.type === 'remove') { - text += lineBreakData.isLineBreak - ? BLOCK_SUGGESTION - : BLOCK_SUGGESTION + TYPE_TEXT_MAP[node.type](node); + if (lineBreakData?.id === keyId2SuggestionId(id)) { + if (lineBreakData.type === 'insert') { + newText += lineBreakData.isLineBreak + ? BLOCK_SUGGESTION + : BLOCK_SUGGESTION + TYPE_TEXT_MAP[node.type](node); + } else if (lineBreakData.type === 'remove') { + text += lineBreakData.isLineBreak + ? BLOCK_SUGGESTION + : BLOCK_SUGGESTION + TYPE_TEXT_MAP[node.type](node); + } } } }); @@ -440,7 +409,7 @@ export const useResolveSuggestion = ( const keyId = getSuggestionKey(id); if (nodeData.type === 'update') { - return res.push({ + res.push({ comments, createdAt, keyId, @@ -451,9 +420,8 @@ export const useResolveSuggestion = ( type: 'update', userId: nodeData.userId, }); - } - if (newText.length > 0 && text.length > 0) { - return res.push({ + } else if (newText.length > 0 && text.length > 0) { + res.push({ comments, createdAt, keyId, @@ -463,9 +431,8 @@ export const useResolveSuggestion = ( type: 'replace', userId: nodeData.userId, }); - } - if (newText.length > 0) { - return res.push({ + } else if (newText.length > 0) { + res.push({ comments, createdAt, keyId, @@ -474,9 +441,8 @@ export const useResolveSuggestion = ( type: 'insert', userId: nodeData.userId, }); - } - if (text.length > 0) { - return res.push({ + } else if (text.length > 0) { + res.push({ comments, createdAt, keyId, @@ -503,6 +469,4 @@ export const useResolveSuggestion = ( export const isResolvedSuggestion = ( suggestion: ResolvedSuggestion | TDiscussion -): suggestion is ResolvedSuggestion => { - return 'suggestionId' in suggestion; -}; +): suggestion is ResolvedSuggestion => 'suggestionId' in suggestion; diff --git a/templates/plate-playground-template/src/components/ui/blockquote-node-static.tsx b/templates/plate-playground-template/src/components/ui/blockquote-node-static.tsx index 92750a12bb..493161f565 100644 --- a/templates/plate-playground-template/src/components/ui/blockquote-node-static.tsx +++ b/templates/plate-playground-template/src/components/ui/blockquote-node-static.tsx @@ -1,6 +1,4 @@ -import * as React from 'react'; - -import { type SlateElementProps, SlateElement } from 'platejs'; +import { SlateElement, type SlateElementProps } from 'platejs/static'; export function BlockquoteElementStatic(props: SlateElementProps) { return ( diff --git a/templates/plate-playground-template/src/components/ui/blockquote-node.tsx b/templates/plate-playground-template/src/components/ui/blockquote-node.tsx index ba5bec4e81..e6edf24336 100644 --- a/templates/plate-playground-template/src/components/ui/blockquote-node.tsx +++ b/templates/plate-playground-template/src/components/ui/blockquote-node.tsx @@ -1,6 +1,6 @@ 'use client'; -import { type PlateElementProps, PlateElement } from 'platejs/react'; +import { PlateElement, type PlateElementProps } from 'platejs/react'; export function BlockquoteElement(props: PlateElementProps) { return ( diff --git a/templates/plate-playground-template/src/components/ui/button.tsx b/templates/plate-playground-template/src/components/ui/button.tsx index 21409a0666..7826cd0d05 100644 --- a/templates/plate-playground-template/src/components/ui/button.tsx +++ b/templates/plate-playground-template/src/components/ui/button.tsx @@ -1,40 +1,40 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import type * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: - "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40', outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50', secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", + 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + 'icon-sm': 'size-8', + 'icon-lg': 'size-10', }, }, defaultVariants: { - variant: "default", - size: "default", + variant: 'default', + size: 'default', }, } -) +); function Button({ className, @@ -42,19 +42,19 @@ function Button({ size, asChild = false, ...props -}: React.ComponentProps<"button"> & +}: React.ComponentProps<'button'> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : 'button'; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/templates/plate-playground-template/src/components/ui/calendar.tsx b/templates/plate-playground-template/src/components/ui/calendar.tsx index 4d7c46afb5..0a1dd34eab 100644 --- a/templates/plate-playground-template/src/components/ui/calendar.tsx +++ b/templates/plate-playground-template/src/components/ui/calendar.tsx @@ -1,175 +1,177 @@ -"use client" +'use client'; -import * as React from "react" import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, -} from "lucide-react" -import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" - -import { cn } from "@/lib/utils" -import { Button, buttonVariants } from "@/components/ui/button" +} from 'lucide-react'; +import * as React from 'react'; +import { + type DayButton, + DayPicker, + getDefaultClassNames, +} from 'react-day-picker'; +import { Button, buttonVariants } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; function Calendar({ className, classNames, showOutsideDays = true, - captionLayout = "label", - buttonVariant = "ghost", + captionLayout = 'label', + buttonVariant = 'ghost', formatters, components, ...props }: React.ComponentProps & { - buttonVariant?: React.ComponentProps["variant"] + buttonVariant?: React.ComponentProps['variant']; }) { - const defaultClassNames = getDefaultClassNames() + const defaultClassNames = getDefaultClassNames(); return ( svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className )} - captionLayout={captionLayout} - formatters={{ - formatMonthDropdown: (date) => - date.toLocaleString("default", { month: "short" }), - ...formatters, - }} classNames={{ - root: cn("w-fit", defaultClassNames.root), + root: cn('w-fit', defaultClassNames.root), months: cn( - "flex gap-4 flex-col md:flex-row relative", + 'relative flex flex-col gap-4 md:flex-row', defaultClassNames.months ), - month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + month: cn('flex w-full flex-col gap-4', defaultClassNames.month), nav: cn( - "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + 'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', defaultClassNames.nav ), button_previous: cn( buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + 'size-(--cell-size) select-none p-0 aria-disabled:opacity-50', defaultClassNames.button_previous ), button_next: cn( buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + 'size-(--cell-size) select-none p-0 aria-disabled:opacity-50', defaultClassNames.button_next ), month_caption: cn( - "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + 'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)', defaultClassNames.month_caption ), dropdowns: cn( - "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + 'flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm', defaultClassNames.dropdowns ), dropdown_root: cn( - "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + 'relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50', defaultClassNames.dropdown_root ), dropdown: cn( - "absolute bg-popover inset-0 opacity-0", + 'absolute inset-0 bg-popover opacity-0', defaultClassNames.dropdown ), caption_label: cn( - "select-none font-medium", - captionLayout === "label" - ? "text-sm" - : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + 'select-none font-medium', + captionLayout === 'label' + ? 'text-sm' + : 'flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground', defaultClassNames.caption_label ), - table: "w-full border-collapse", - weekdays: cn("flex", defaultClassNames.weekdays), + table: 'w-full border-collapse', + weekdays: cn('flex', defaultClassNames.weekdays), weekday: cn( - "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + 'flex-1 select-none rounded-md font-normal text-[0.8rem] text-muted-foreground', defaultClassNames.weekday ), - week: cn("flex w-full mt-2", defaultClassNames.week), + week: cn('mt-2 flex w-full', defaultClassNames.week), week_number_header: cn( - "select-none w-(--cell-size)", + 'w-(--cell-size) select-none', defaultClassNames.week_number_header ), week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", + 'select-none text-[0.8rem] text-muted-foreground', defaultClassNames.week_number ), day: cn( - "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + 'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md', + props.showWeekNumber + ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-md' + : '[&:first-child[data-selected=true]_button]:rounded-l-md', defaultClassNames.day ), range_start: cn( - "rounded-l-md bg-accent", + 'rounded-l-md bg-accent', defaultClassNames.range_start ), - range_middle: cn("rounded-none", defaultClassNames.range_middle), - range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + range_middle: cn('rounded-none', defaultClassNames.range_middle), + range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end), today: cn( - "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + 'rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none', defaultClassNames.today ), outside: cn( - "text-muted-foreground aria-selected:text-muted-foreground", + 'text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside ), disabled: cn( - "text-muted-foreground opacity-50", + 'text-muted-foreground opacity-50', defaultClassNames.disabled ), - hidden: cn("invisible", defaultClassNames.hidden), + hidden: cn('invisible', defaultClassNames.hidden), ...classNames, }} components={{ - Root: ({ className, rootRef, ...props }) => { - return ( -
- ) - }, + Root: ({ className, rootRef, ...props }) => ( +
+ ), Chevron: ({ className, orientation, ...props }) => { - if (orientation === "left") { + if (orientation === 'left') { return ( - - ) + + ); } - if (orientation === "right") { + if (orientation === 'right') { return ( - ) + ); } return ( - - ) + + ); }, DayButton: CalendarDayButton, - WeekNumber: ({ children, ...props }) => { - return ( - -
- {children} -
- - ) - }, + WeekNumber: ({ children, ...props }) => ( + +
+ {children} +
+ + ), ...components, }} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString('default', { month: 'short' }), + ...formatters, + }} + showOutsideDays={showOutsideDays} {...props} /> - ) + ); } function CalendarDayButton({ @@ -178,36 +180,36 @@ function CalendarDayButton({ modifiers, ...props }: React.ComponentProps) { - const defaultClassNames = getDefaultClassNames() + const defaultClassNames = getDefaultClassNames(); - const ref = React.useRef(null) + const ref = React.useRef(null); React.useEffect(() => { - if (modifiers.focused) ref.current?.focus() - }, [modifiers.focused]) + if (modifiers.focused) ref.current?.focus(); + }, [modifiers.focused]); return ( diff --git a/templates/plate-playground-template/src/components/ui/caption.tsx b/templates/plate-playground-template/src/components/ui/caption.tsx index b43caba510..3f60a37238 100644 --- a/templates/plate-playground-template/src/components/ui/caption.tsx +++ b/templates/plate-playground-template/src/components/ui/caption.tsx @@ -1,9 +1,5 @@ 'use client'; -import * as React from 'react'; - -import type { VariantProps } from 'class-variance-authority'; - import { Caption as CaptionPrimitive, CaptionTextarea as CaptionTextareaPrimitive, @@ -11,7 +7,9 @@ import { useCaptionButtonState, } from '@platejs/caption/react'; import { createPrimitiveComponent } from '@udecode/cn'; +import type { VariantProps } from 'class-variance-authority'; import { cva } from 'class-variance-authority'; +import type * as React from 'react'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; diff --git a/templates/plate-playground-template/src/components/ui/checkbox.tsx b/templates/plate-playground-template/src/components/ui/checkbox.tsx index cb0b07b468..e8f243f78f 100644 --- a/templates/plate-playground-template/src/components/ui/checkbox.tsx +++ b/templates/plate-playground-template/src/components/ui/checkbox.tsx @@ -1,10 +1,10 @@ -"use client" +'use client'; -import * as React from "react" -import * as CheckboxPrimitive from "@radix-ui/react-checkbox" -import { CheckIcon } from "lucide-react" +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { CheckIcon } from 'lucide-react'; +import type * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; function Checkbox({ className, @@ -12,21 +12,21 @@ function Checkbox({ }: React.ComponentProps) { return ( - ) + ); } -export { Checkbox } +export { Checkbox }; diff --git a/templates/plate-playground-template/src/components/ui/code-block-node-static.tsx b/templates/plate-playground-template/src/components/ui/code-block-node-static.tsx index f634783b1b..250cebcafc 100644 --- a/templates/plate-playground-template/src/components/ui/code-block-node-static.tsx +++ b/templates/plate-playground-template/src/components/ui/code-block-node-static.tsx @@ -1,12 +1,10 @@ -import * as React from 'react'; - +import type { TCodeBlockElement } from 'platejs'; import { - type SlateElementProps, - type SlateLeafProps, - type TCodeBlockElement, SlateElement, + type SlateElementProps, SlateLeaf, -} from 'platejs'; + type SlateLeafProps, +} from 'platejs/static'; export function CodeBlockElementStatic( props: SlateElementProps diff --git a/templates/plate-playground-template/src/components/ui/code-block-node.tsx b/templates/plate-playground-template/src/components/ui/code-block-node.tsx index d817ba6e68..05fef30d87 100644 --- a/templates/plate-playground-template/src/components/ui/code-block-node.tsx +++ b/templates/plate-playground-template/src/components/ui/code-block-node.tsx @@ -1,17 +1,18 @@ 'use client'; -import * as React from 'react'; - import { formatCodeBlock, isLangSupported } from '@platejs/code-block'; import { BracesIcon, Check, CheckIcon, CopyIcon } from 'lucide-react'; -import { type TCodeBlockElement, type TCodeSyntaxLeaf, NodeApi } from 'platejs'; +import { NodeApi, type TCodeBlockElement, type TCodeSyntaxLeaf } from 'platejs'; import { - type PlateElementProps, - type PlateLeafProps, PlateElement, + type PlateElementProps, PlateLeaf, + type PlateLeafProps, + useEditorRef, + useElement, + useReadOnly, } from 'platejs/react'; -import { useEditorRef, useElement, useReadOnly } from 'platejs/react'; +import * as React from 'react'; import { Button } from '@/components/ui/button'; import { @@ -43,16 +44,16 @@ export function CodeBlockElement(props: PlateElementProps) {
{isLangSupported(element.lang) && ( @@ -61,10 +62,10 @@ export function CodeBlockElement(props: PlateElementProps) { NodeApi.string(element)} + variant="ghost" />
@@ -93,14 +94,14 @@ function CodeBlockCombobox() { if (readOnly) return null; return ( - + - -
diff --git a/templates/plate-playground-template/src/components/ui/command.tsx b/templates/plate-playground-template/src/components/ui/command.tsx index 8cb4ca7a58..74fdc8659b 100644 --- a/templates/plate-playground-template/src/components/ui/command.tsx +++ b/templates/plate-playground-template/src/components/ui/command.tsx @@ -1,17 +1,16 @@ -"use client" +'use client'; -import * as React from "react" -import { Command as CommandPrimitive } from "cmdk" -import { SearchIcon } from "lucide-react" - -import { cn } from "@/lib/utils" +import { Command as CommandPrimitive } from 'cmdk'; +import { SearchIcon } from 'lucide-react'; +import type * as React from 'react'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@/components/ui/dialog" +} from '@/components/ui/dialog'; +import { cn } from '@/lib/utils'; function Command({ className, @@ -19,28 +18,28 @@ function Command({ }: React.ComponentProps) { return ( - ) + ); } function CommandDialog({ - title = "Command Palette", - description = "Search for a command to run...", + title = 'Command Palette', + description = 'Search for a command to run...', children, className, showCloseButton = true, ...props }: React.ComponentProps & { - title?: string - description?: string - className?: string - showCloseButton?: boolean + title?: string; + description?: string; + className?: string; + showCloseButton?: boolean; }) { return ( @@ -49,15 +48,15 @@ function CommandDialog({ {description} - + {children} - ) + ); } function CommandInput({ @@ -66,20 +65,20 @@ function CommandInput({ }: React.ComponentProps) { return (
- ) + ); } function CommandList({ @@ -88,14 +87,14 @@ function CommandList({ }: React.ComponentProps) { return ( - ) + ); } function CommandEmpty({ @@ -103,11 +102,11 @@ function CommandEmpty({ }: React.ComponentProps) { return ( - ) + ); } function CommandGroup({ @@ -116,14 +115,14 @@ function CommandGroup({ }: React.ComponentProps) { return ( - ) + ); } function CommandSeparator({ @@ -132,11 +131,11 @@ function CommandSeparator({ }: React.ComponentProps) { return ( - ) + ); } function CommandItem({ @@ -145,30 +144,30 @@ function CommandItem({ }: React.ComponentProps) { return ( - ) + ); } function CommandShortcut({ className, ...props -}: React.ComponentProps<"span">) { +}: React.ComponentProps<'span'>) { return ( - ) + ); } export { @@ -181,4 +180,4 @@ export { CommandItem, CommandShortcut, CommandSeparator, -} +}; diff --git a/templates/plate-playground-template/src/components/ui/comment-node-static.tsx b/templates/plate-playground-template/src/components/ui/comment-node-static.tsx index 8321664b95..b9febb84da 100644 --- a/templates/plate-playground-template/src/components/ui/comment-node-static.tsx +++ b/templates/plate-playground-template/src/components/ui/comment-node-static.tsx @@ -1,8 +1,6 @@ -import * as React from 'react'; - -import type { SlateLeafProps, TCommentText } from 'platejs'; - -import { SlateLeaf } from 'platejs'; +import type { TCommentText } from 'platejs'; +import type { SlateLeafProps } from 'platejs/static'; +import { SlateLeaf } from 'platejs/static'; export function CommentLeafStatic(props: SlateLeafProps) { return ( diff --git a/templates/plate-playground-template/src/components/ui/comment-node.tsx b/templates/plate-playground-template/src/components/ui/comment-node.tsx index 4eb946df37..8a780e5acb 100644 --- a/templates/plate-playground-template/src/components/ui/comment-node.tsx +++ b/templates/plate-playground-template/src/components/ui/comment-node.tsx @@ -1,15 +1,12 @@ 'use client'; -import * as React from 'react'; +import { getCommentCount } from '@platejs/comment'; import type { TCommentText } from 'platejs'; import type { PlateLeafProps } from 'platejs/react'; - -import { getCommentCount } from '@platejs/comment'; import { PlateLeaf, useEditorPlugin, usePluginOption } from 'platejs/react'; - -import { cn } from '@/lib/utils'; import { commentPlugin } from '@/components/editor/plugins/comment-kit'; +import { cn } from '@/lib/utils'; export function CommentLeaf(props: PlateLeafProps) { const { children, leaf } = props; @@ -26,6 +23,12 @@ export function CommentLeaf(props: PlateLeafProps) { return ( setOption('activeId', currentId ?? null), + onMouseEnter: () => setOption('hoverId', currentId ?? null), + onMouseLeave: () => setOption('hoverId', null), + }} className={cn( 'border-b-2 border-b-highlight/[.36] bg-highlight/[.13] transition-colors duration-200', (isHover || isActive) && 'border-b-highlight bg-highlight/25', @@ -34,12 +37,6 @@ export function CommentLeaf(props: PlateLeafProps) { isOverlapping && 'border-b-highlight bg-highlight/45' )} - attributes={{ - ...props.attributes, - onClick: () => setOption('activeId', currentId ?? null), - onMouseEnter: () => setOption('hoverId', currentId ?? null), - onMouseLeave: () => setOption('hoverId', null), - }} > {children} diff --git a/templates/plate-playground-template/src/components/ui/comment-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/comment-toolbar-button.tsx index 08cd6feb93..da06be20d1 100644 --- a/templates/plate-playground-template/src/components/ui/comment-toolbar-button.tsx +++ b/templates/plate-playground-template/src/components/ui/comment-toolbar-button.tsx @@ -1,7 +1,5 @@ 'use client'; -import * as React from 'react'; - import { MessageSquareTextIcon } from 'lucide-react'; import { useEditorRef } from 'platejs/react'; @@ -14,10 +12,10 @@ export function CommentToolbarButton() { return ( { editor.getTransforms(commentPlugin).comment.setDraft(); }} - data-plate-prevent-overlay tooltip="Comment" > diff --git a/templates/plate-playground-template/src/components/ui/comment.tsx b/templates/plate-playground-template/src/components/ui/comment.tsx index 40b0f1f43f..c933207f48 100644 --- a/templates/plate-playground-template/src/components/ui/comment.tsx +++ b/templates/plate-playground-template/src/components/ui/comment.tsx @@ -1,9 +1,5 @@ 'use client'; -import * as React from 'react'; - -import type { CreatePlateEditorOptions } from 'platejs/react'; - import { getCommentKey, getDraftCommentKey } from '@platejs/comment'; import { CommentPlugin, useCommentId } from '@platejs/comment/react'; import { @@ -20,7 +16,8 @@ import { TrashIcon, XIcon, } from 'lucide-react'; -import { type Value, KEYS, nanoid, NodeApi } from 'platejs'; +import { KEYS, NodeApi, nanoid, type Value } from 'platejs'; +import type { CreatePlateEditorOptions } from 'platejs/react'; import { Plate, useEditorPlugin, @@ -28,7 +25,12 @@ import { usePlateEditor, usePluginOption, } from 'platejs/react'; - +import * as React from 'react'; +import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit'; +import { + discussionPlugin, + type TDiscussion, +} from '@/components/editor/plugins/discussion-kit'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { @@ -39,22 +41,17 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { cn } from '@/lib/utils'; -import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit'; -import { - type TDiscussion, - discussionPlugin, -} from '@/components/editor/plugins/discussion-kit'; import { Editor, EditorContainer } from './editor'; -export interface TComment { +export type TComment = { id: string; contentRich: Value; createdAt: Date; discussionId: string; isEdited: boolean; userId: string; -} +}; export function Comment(props: { comment: TComment; @@ -183,12 +180,12 @@ export function Comment(props: { {userInfo?.name?.[0]} -

+

{/* Replace to your own backend or refer to potion */} {userInfo?.name}

-
+
{formatCommentDate(new Date(comment.createdAt))} @@ -199,16 +196,18 @@ export function Comment(props: {
{index === 0 && ( )} { setTimeout(() => { commentEditor.tf.focus({ edge: 'endEditor' }); @@ -220,8 +219,6 @@ export function Comment(props: { void removeDiscussion(comment.discussionId); } }} - comment={comment} - dropdownOpen={dropdownOpen} setDropdownOpen={setDropdownOpen} setEditingId={setEditingId} /> @@ -230,7 +227,7 @@ export function Comment(props: {
{isFirst && showDocumentContent && ( -
+
{discussionLength > 1 && (
)} @@ -243,24 +240,24 @@ export function Comment(props: { {!isLast && (
)} - + onEditorClick?.()} + variant="comment" /> {isEditing && (
@@ -559,14 +556,15 @@ export function CommentCreateForm({
{ setCommentValue(value); }} - editor={commentEditor} > { if (e.key === 'Enter' && !e.shiftKey) { @@ -575,19 +573,18 @@ export function CommentCreateForm({ } }} placeholder="Reply..." - autoComplete="off" - autoFocus={autoFocus} + variant="comment" /> @@ -417,12 +419,12 @@ function EmojiPickerSearchAndClear({ function EmojiPreview({ emoji }: Pick) { return ( -
+
{emoji?.skins[0].native}
-
{emoji?.name}
+
{emoji?.name}
{`:${emoji?.id}:`}
@@ -431,10 +433,10 @@ function EmojiPreview({ emoji }: Pick) { function NoEmoji({ i18n }: Pick) { return ( -
+
😢
-
+
{i18n.searchNoResultsTitle}
{i18n.searchNoResultsSubtitle}
@@ -445,10 +447,10 @@ function NoEmoji({ i18n }: Pick) { function PickAnEmoji({ i18n }: Pick) { return ( -
+
☝️
-
{i18n.pick}
+
{i18n.pick}
); @@ -489,8 +491,8 @@ function EmojiPickerNavigation({ return (
@@ -117,8 +116,8 @@ export function LinkFloatingToolbar({
@@ -158,11 +157,11 @@ export function LinkFloatingToolbar({ return ( <> -
+
{input}
-
+
{editContent}
@@ -191,6 +190,7 @@ function LinkOpenButton() { return ( { e.stopPropagation(); }} - aria-label="Open link in a new tab" target="_blank" > diff --git a/templates/plate-playground-template/src/components/ui/list-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/list-toolbar-button.tsx index 8013ff98fd..dce4e62067 100644 --- a/templates/plate-playground-template/src/components/ui/list-toolbar-button.tsx +++ b/templates/plate-playground-template/src/components/ui/list-toolbar-button.tsx @@ -1,7 +1,5 @@ 'use client'; -import * as React from 'react'; - import { ListStyleType, someList, toggleList } from '@platejs/list'; import { useIndentTodoToolBarButton, @@ -9,6 +7,7 @@ import { } from '@platejs/list/react'; import { List, ListOrdered, ListTodoIcon } from 'lucide-react'; import { useEditorRef, useEditorSelector } from 'platejs/react'; +import * as React from 'react'; import { DropdownMenu, @@ -43,17 +42,17 @@ export function BulletedListToolbarButton() { { toggleList(editor, { listStyleType: ListStyleType.Disc, }); }} - data-state={pressed ? 'on' : 'off'} > - + @@ -123,17 +122,17 @@ export function NumberedListToolbarButton() { toggleList(editor, { listStyleType: ListStyleType.Decimal, }) } - data-state={pressed ? 'on' : 'off'} > - + diff --git a/templates/plate-playground-template/src/components/ui/mark-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/mark-toolbar-button.tsx index 1f9405756f..8baf49d486 100644 --- a/templates/plate-playground-template/src/components/ui/mark-toolbar-button.tsx +++ b/templates/plate-playground-template/src/components/ui/mark-toolbar-button.tsx @@ -1,8 +1,7 @@ 'use client'; -import * as React from 'react'; - import { useMarkToolbarButton, useMarkToolbarButtonState } from 'platejs/react'; +import type * as React from 'react'; import { ToolbarButton } from './toolbar'; diff --git a/templates/plate-playground-template/src/components/ui/media-audio-node-static.tsx b/templates/plate-playground-template/src/components/ui/media-audio-node-static.tsx index a84b398b50..45ae5096fc 100644 --- a/templates/plate-playground-template/src/components/ui/media-audio-node-static.tsx +++ b/templates/plate-playground-template/src/components/ui/media-audio-node-static.tsx @@ -1,15 +1,13 @@ -import * as React from 'react'; - -import type { SlateElementProps, TAudioElement } from 'platejs'; - -import { SlateElement } from 'platejs'; +import type { TAudioElement } from 'platejs'; +import type { SlateElementProps } from 'platejs/static'; +import { SlateElement } from 'platejs/static'; export function AudioElementStatic(props: SlateElementProps) { return (
-
{props.children} diff --git a/templates/plate-playground-template/src/components/ui/media-audio-node.tsx b/templates/plate-playground-template/src/components/ui/media-audio-node.tsx index 320d219a93..bb422a843e 100644 --- a/templates/plate-playground-template/src/components/ui/media-audio-node.tsx +++ b/templates/plate-playground-template/src/components/ui/media-audio-node.tsx @@ -1,12 +1,9 @@ 'use client'; -import * as React from 'react'; - -import type { TAudioElement } from 'platejs'; -import type { PlateElementProps } from 'platejs/react'; - import { useMediaState } from '@platejs/media/react'; import { ResizableProvider } from '@platejs/resizable'; +import type { TAudioElement } from 'platejs'; +import type { PlateElementProps } from 'platejs/react'; import { PlateElement, withHOC } from 'platejs/react'; import { Caption, CaptionTextarea } from './caption'; @@ -23,14 +20,14 @@ export const AudioElement = withHOC( contentEditable={false} >
-
- + diff --git a/templates/plate-playground-template/src/components/ui/media-embed-node.tsx b/templates/plate-playground-template/src/components/ui/media-embed-node.tsx index d380e74674..3c52d70bee 100644 --- a/templates/plate-playground-template/src/components/ui/media-embed-node.tsx +++ b/templates/plate-playground-template/src/components/ui/media-embed-node.tsx @@ -1,16 +1,14 @@ 'use client'; -import * as React from 'react'; -import LiteYouTubeEmbed from 'react-lite-youtube-embed'; -import { Tweet } from 'react-tweet'; - -import type { TMediaEmbedElement } from 'platejs'; -import type { PlateElementProps } from 'platejs/react'; - import { parseTwitterUrl, parseVideoUrl } from '@platejs/media'; import { MediaEmbedPlugin, useMediaState } from '@platejs/media/react'; import { ResizableProvider, useResizableValue } from '@platejs/resizable'; + +import type { TMediaEmbedElement } from 'platejs'; +import type { PlateElementProps } from 'platejs/react'; import { PlateElement, withHOC } from 'platejs/react'; +import LiteYouTubeEmbed from 'react-lite-youtube-embed'; +import { Tweet } from 'react-tweet'; import { cn } from '@/lib/utils'; @@ -68,14 +66,14 @@ export const MediaEmbedElement = withHOC( wrapperClass={cn( 'rounded-sm', focused && selected && 'ring-2 ring-ring ring-offset-2', - 'relative block cursor-pointer bg-black bg-cover bg-center [contain:content]', + 'relative block cursor-pointer bg-black bg-center bg-cover [contain:content]', '[&.lyt-activated]:before:absolute [&.lyt-activated]:before:top-0 [&.lyt-activated]:before:h-[60px] [&.lyt-activated]:before:w-full [&.lyt-activated]:before:bg-top [&.lyt-activated]:before:bg-repeat-x [&.lyt-activated]:before:pb-[50px] [&.lyt-activated]:before:[transition:all_0.2s_cubic-bezier(0,_0,_0.2,_1)]', '[&.lyt-activated]:before:bg-[url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)]', 'after:block after:pb-[var(--aspect-ratio)] after:content-[""]', '[&_>_iframe]:absolute [&_>_iframe]:top-0 [&_>_iframe]:left-0 [&_>_iframe]:size-full', '[&_>_.lty-playbtn]:z-1 [&_>_.lty-playbtn]:h-[46px] [&_>_.lty-playbtn]:w-[70px] [&_>_.lty-playbtn]:rounded-[14%] [&_>_.lty-playbtn]:bg-[#212121] [&_>_.lty-playbtn]:opacity-80 [&_>_.lty-playbtn]:[transition:all_0.2s_cubic-bezier(0,_0,_0.2,_1)]', '[&:hover_>_.lty-playbtn]:bg-[red] [&:hover_>_.lty-playbtn]:opacity-100', - '[&_>_.lty-playbtn]:before:border-y-[11px] [&_>_.lty-playbtn]:before:border-r-0 [&_>_.lty-playbtn]:before:border-l-[19px] [&_>_.lty-playbtn]:before:border-[transparent_transparent_transparent_#fff] [&_>_.lty-playbtn]:before:content-[""]', + '[&_>_.lty-playbtn]:before:border-[transparent_transparent_transparent_#fff] [&_>_.lty-playbtn]:before:border-y-[11px] [&_>_.lty-playbtn]:before:border-r-0 [&_>_.lty-playbtn]:before:border-l-[19px] [&_>_.lty-playbtn]:before:content-[""]', '[&_>_.lty-playbtn]:absolute [&_>_.lty-playbtn]:top-1/2 [&_>_.lty-playbtn]:left-1/2 [&_>_.lty-playbtn]:[transform:translate3d(-50%,-50%,0)]', '[&_>_.lty-playbtn]:before:absolute [&_>_.lty-playbtn]:before:top-1/2 [&_>_.lty-playbtn]:before:left-1/2 [&_>_.lty-playbtn]:before:[transform:translate3d(-50%,-50%,0)]', '[&.lyt-activated]:cursor-[unset]', @@ -93,14 +91,14 @@ export const MediaEmbedElement = withHOC( )} >