diff --git a/.changeset/beige-scissors-complain.md b/.changeset/beige-scissors-complain.md new file mode 100644 index 0000000000..7dcf53311c --- /dev/null +++ b/.changeset/beige-scissors-complain.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-core': patch +--- + +Fix DefaultLeaf and DefaultElement props diff --git a/.changeset/chilly-dolls-study.md b/.changeset/chilly-dolls-study.md new file mode 100644 index 0000000000..1349e41f52 --- /dev/null +++ b/.changeset/chilly-dolls-study.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-callout': patch +--- + +useCalloutEmojiPicker diff --git a/.changeset/curvy-mice-relate.md b/.changeset/curvy-mice-relate.md new file mode 100644 index 0000000000..69a2c6245a --- /dev/null +++ b/.changeset/curvy-mice-relate.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-markdown': patch +--- + +New `deserializeInlineMd` `serializeInlineMd` `stripMarkdownBlocks` `stripMarkdownInline` diff --git a/.changeset/eight-gorillas-teach.md b/.changeset/eight-gorillas-teach.md new file mode 100644 index 0000000000..d4fb141437 --- /dev/null +++ b/.changeset/eight-gorillas-teach.md @@ -0,0 +1,7 @@ +--- +'@udecode/plate-selection': patch +--- + +BlockSelectionPlugin: New `tf.setBlockSelectionIndent` `tf.insertBlocksAndSelect` + +BlockMenuPlugin: Now when the left mouse button is clicked and the menu is open, the default event will not be prevented. diff --git a/.changeset/fresh-spoons-float.md b/.changeset/fresh-spoons-float.md new file mode 100644 index 0000000000..8a9218093a --- /dev/null +++ b/.changeset/fresh-spoons-float.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-math': patch +--- + +New `editor.tf.insert.equation` `editor.tf.insert.inlineEquation` diff --git a/.changeset/new-socks-know.md b/.changeset/new-socks-know.md new file mode 100644 index 0000000000..1e3a10db72 --- /dev/null +++ b/.changeset/new-socks-know.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-callout': patch +--- + +New `editor.tf.insert.callout` diff --git a/.changeset/small-months-report.md b/.changeset/small-months-report.md new file mode 100644 index 0000000000..ca3ec99f90 --- /dev/null +++ b/.changeset/small-months-report.md @@ -0,0 +1,6 @@ +--- +'@udecode/plate-media': patch +--- + +New `editor.tf.insert.audioPlaceholder` `editor.tf.insert.filePlaceholder` `editor.tf.insert.imagePlaceholder` `editor.tf.insert.videoPlaceholder` + diff --git a/.changeset/sour-cobras-tease.md b/.changeset/sour-cobras-tease.md new file mode 100644 index 0000000000..e8f705a775 --- /dev/null +++ b/.changeset/sour-cobras-tease.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-ai': patch +--- + + `CopilotPlugin`, `AIPlugin` is ready from this version. diff --git a/README.md b/README.md index 39f81de7fb..525cadba56 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,12 @@ Plate You can choose one of the following templates to get started: -| Option | NextJS | Tailwind | Plate | Plugins | -| --------------------------------------------------------------------------------- | ------ | -------- | ----- | ------- | -| [Plate playground template](https://github.com/udecode/plate-playground-template) | ✅ | ✅ | ✅ | ✅ | -| [Plate minimal template](https://github.com/udecode/plate-template) | ✅ | ✅ | ✅ | | -| [NextJS template](https://platejs.org/docs/components/installation/next) | ✅ | ✅ | | | +| Option | NextJS | Plate | Plugins |AI & Backend | +| --------------------------------------------------------------------------------- | ------ | ----- | ------- |-------- | +| [Notion clone template](https://pro.platejs.org/docs/templates/potion) | ✅ | ✅ | ✅ |✅ | +| [Plate playground template](https://github.com/udecode/plate-playground-template) | ✅ | ✅ | ✅ | | +| [Plate minimal template](https://github.com/udecode/plate-template) | ✅ | ✅ | | | +| [NextJS template](https://platejs.org/docs/components/installation/next) | ✅ | | | | ## Documentation diff --git a/apps/www/content/docs/ai.mdx b/apps/www/content/docs/ai.mdx new file mode 100644 index 0000000000..5464b3256c --- /dev/null +++ b/apps/www/content/docs/ai.mdx @@ -0,0 +1,432 @@ +--- +title: AI +description: AI menu with commands, streaming responses in a preview or directly into the editor. +docs: + - route: https://pro.platejs.org/docs/examples/ai + title: AI Menu +--- + + + + + +## Features + +- Combobox menu with predefined commands: + - Generate: continue writing, add summary, explain + - Edit: improve writing, make it longer or shorter, fix spelling & grammar, simplify language +- Three trigger modes: + - Cursor mode: trigger at block end + - Selection mode: trigger with selected text + - Block selection mode: trigger with selected blocks +- Streaming responses in preview or direct editor insertion +- Markdown support +- Built-in support for Vercel AI SDK chat API + + + +## Installation + +```bash +npm install @udecode/plate-ai @udecode/plate-selection @udecode/plate-markdown @udecode/plate-basic-marks +``` + +## Usage + +### Plugins + +```tsx +import { withProps } from '@udecode/cn'; +import { AIChatPlugin, AIPlugin } from '@udecode/plate-ai/react'; +import { + BoldPlugin, + CodePlugin, + ItalicPlugin, + StrikethroughPlugin, + UnderlinePlugin, +} from '@udecode/plate-basic-marks/react'; +import { PlateLeaf, createPlateEditor } from '@udecode/plate-common/react'; +import { LinkPlugin } from '@udecode/plate-link/react'; +import { MarkdownPlugin } from '@udecode/plate-markdown'; +``` + +```tsx +export const createAIEditor = () => { + const editor = createPlateEditor({ + id: 'ai', + override: { + components: { + [BoldPlugin.key]: withProps(PlateLeaf, { as: 'strong' }), + [CodePlugin.key]: CodeLeaf, + [ItalicPlugin.key]: withProps(PlateLeaf, { as: 'em' }), + [LinkPlugin.key]: LinkElement, + [StrikethroughPlugin.key]: withProps(PlateLeaf, { as: 's' }), + [UnderlinePlugin.key]: withProps(PlateLeaf, { as: 'u' }), + }, + }, + plugins: [ + BoldPlugin, + ItalicPlugin, + UnderlinePlugin, + StrikethroughPlugin, + CodePlugin, + ], + value: [{ children: [{ text: '' }], type: 'p' }], + }); + + return editor; +}; + +const systemCommon = `\ +You are an advanced AI-powered note-taking assistant, designed to enhance productivity and creativity in note management. +Respond directly to user prompts with clear, concise, and relevant content. Maintain a neutral, helpful tone. + +Rules: +- is the entire note the user is working on. +- is a reminder of how you should reply to INSTRUCTIONS. It does not apply to questions. +- Anything else is the user prompt. +- Your response should be tailored to the user's prompt, providing precise assistance to optimize note management. +- For INSTRUCTIONS: Follow the exactly. Provide ONLY the content to be inserted or replaced. No explanations or comments. +- For QUESTIONS: Provide a helpful and concise answer. You may include brief explanations if necessary. +- CRITICAL: Distinguish between INSTRUCTIONS and QUESTIONS. Instructions typically ask you to modify or add content. Questions ask for information or clarification. +`; + +const systemDefault = `\ +${systemCommon} +- is the current block of text the user is working on. +- Ensure your output can seamlessly fit into the existing structure. +- CRITICAL: Provide only a single block of text. DO NOT create multiple paragraphs or separate blocks. + +{block} + +`; + +const systemSelecting = `\ +${systemCommon} +- is the block of text containing the user's selection, providing context. +- Ensure your output can seamlessly fit into the existing structure. +- is the specific text the user has selected in the block and wants to modify or ask about. +- Consider the context provided by , but only modify . Your response should be a direct replacement for . + +{block} + + +{selection} + +`; + +const systemBlockSelecting = `\ +${systemCommon} +- represents the full blocks of text the user has selected and wants to modify or ask about. +- Your response should be a direct replacement for the entire . +- Maintain the overall structure and formatting of the selected blocks, unless explicitly instructed otherwise. +- CRITICAL: Provide only the content to replace . Do not add additional blocks or change the block structure unless specifically requested. + +{block} + +`; + +const userDefault = ` +CRITICAL: DO NOT use block formatting. You can only use inline formatting. +CRITICAL: DO NOT start new lines or paragraphs. +NEVER write . + +{prompt}`; + +const userSelecting = ` +If this is a question, provide a helpful and concise answer about . +If this is an instruction, provide ONLY the text to replace . No explanations. +Ensure it fits seamlessly within . If is empty, write ONE random sentence. +NEVER write or . + +{prompt} about `; + +const userBlockSelecting = ` +If this is a question, provide a helpful and concise answer about . +If this is an instruction, provide ONLY the content to replace the entire . No explanations. +Maintain the overall structure unless instructed otherwise. +NEVER write or . + +{prompt} about `; + +export const PROMPT_TEMPLATES = { + systemBlockSelecting, + systemDefault, + systemSelecting, + userBlockSelecting, + userDefault, + userSelecting, +}; + +const plugins = [ + // ...otherPlugins, + MarkdownPlugin.configure({ options: { indentList: true } }), + AIPlugin, + AIChatPlugin.configure({ + options: { + createAIEditor, + promptTemplate: ({ isBlockSelecting, isSelecting }) => { + return isBlockSelecting + ? PROMPT_TEMPLATES.userBlockSelecting + : isSelecting + ? PROMPT_TEMPLATES.userSelecting + : PROMPT_TEMPLATES.userDefault; + }, + scrollContainerSelector: '#scroll_container', + systemTemplate: ({ isBlockSelecting, isSelecting }) => { + return isBlockSelecting + ? PROMPT_TEMPLATES.systemBlockSelecting + : isSelecting + ? PROMPT_TEMPLATES.systemSelecting + : PROMPT_TEMPLATES.systemDefault; + }, + }, + render: { afterEditable: () => }, + }), + SelectionOverlayPlugin, +]; +``` + +- [AIMenu](/docs/components/ai-menu) + +The [SelectionOverlayPlugin](https://pro.platejs.org/docs/components/cursor-overlay): + +- Maintains selection highlight when editor loses focus +- Essential for AI menu and other external input interactions +- Prevents double selection with `data-plate-prevent-overlay` attribute + +### AI SDK + +This plugin is depending on the [ai](https://npmjs.com/package/ai) package: + +- Setup a [route handler](https://sdk.vercel.ai/docs/getting-started/nextjs-app-router#create-a-route-handler) using [streamText](https://sdk.vercel.ai/docs/ai-sdk-core/generating-text#streamtext). +- Wire up [useChat](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) in your [AI menu](/docs/components/ai-menu) component. + + +## Keyboard Shortcuts + + + + Open AI menu in empty block (cursor mode) + + + Open AI menu (cursor or selection mode) + + Close AI menu + + +## Examples + +### Plate UI + +Refer to the preview above. + +### Plate Plus + +- Combobox menu with free-form prompt input +- Additional trigger methods: + - Block menu button + - Slash command menu +- Beautifully crafted UI + + + + +## Plugins + +### AIPlugin + +Extends the editor with AI transforms. + +### AIChatPlugin + +Enables chat operations and streaming text generation in the editor. + + + + Chat helpers returned by [useChat](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat). + + + + +Function to create editor instance for preview mode. + +- **Default:** Creates a basic editor with id 'ai' + + + + + +Specifies how assistant messages are handled: + +- `'chat'`: Shows preview with accept/reject options (default) +- `'insert'`: Directly inserts content into editor +- **Default:** `'chat'` + + + + + +Whether the AI chat is open. + +- **Default:** `false` + + + + + +Template for generating prompts. Supports placeholders: + +- `{block}`: Markdown of blocks in selection +- `{editor}`: Markdown of entire editor content +- `{selection}`: Markdown of current selection +- `{prompt}`: Actual user prompt +- **Default:** `'{prompt}'` + + + + + +CSS selector for scrollable container. + +- **Default:** `'#scroll_container'` + + + + + +Template for system messages. Supports same placeholders as `promptTemplate`. + +- **Default:** `undefined` + + + + +## API + +### editor.aiChat.accept() + +Accepts the current AI suggestion: + +- Removes AI marks from the content +- Hides the AI chat interface +- Focuses the editor + +### editor.aiChat.insertBelow() + +Inserts AI content below the current block. + + + + Editor containing the content to insert. + + + +Handles both block selection and normal selection modes: + +- In block selection: Inserts after the last selected block +- In normal selection: Inserts after the current block + +### editor.aiChat.replaceSelection() + +Replaces the current selection with AI content. + + + + Editor containing the content to replace with. + + + +Handles both block selection and normal selection modes: + +- In block selection: Replaces all selected blocks +- In normal selection: Replaces the current selection + +### editor.aiChat.reset() + +Resets the chat state: + +- Stops any ongoing generation +- Clears chat messages +- Removes all AI nodes from the editor + +### editor.aiChat.submit() + +Submits a prompt to generate AI content. + + + + + + Mode to use. Defaults to 'chat' for selection, 'insert' otherwise. + + + Custom prompt. Uses chat input if not provided. + + + Custom system message. + + + + + +In insert mode, undoes previous AI changes before submitting. + +## Transforms + +### editor.ai.insertNodes() + +Inserts AI-generated nodes with the AI mark. + + + + Nodes to insert with AI mark. + + + + + Target path. Defaults to current selection. + + + + + +### editor.ai.removeMarks() + +Removes AI marks from nodes in the specified location. + + + + + + Location to remove marks from. Defaults to entire document. + + + + + +### editor.ai.removeNodes() + +Removes nodes that have the AI mark. + + + + + + Path to remove nodes from. Defaults to entire document. + + + + + +### editor.ai.undo() + +Special undo operation for AI changes: + +- Undoes the last operation if it was AI-generated +- Removes the redo stack entry to prevent redoing AI operations diff --git a/apps/www/content/docs/api/core/plate.mdx b/apps/www/content/docs/api/core/plate.mdx index abd247f8ef..8f03361f75 100644 --- a/apps/www/content/docs/api/core/plate.mdx +++ b/apps/www/content/docs/api/core/plate.mdx @@ -17,7 +17,7 @@ Children components have access to the plate store. A controlled `editor` instance. This prop is required. -See Slate docs. +See [Slate docs](https://docs.slatejs.org/concepts/09-rendering#decorations). Controlled callback called when the editor state changes. diff --git a/apps/www/content/docs/api/core/store.mdx b/apps/www/content/docs/api/core/store.mdx index 9cc3a5aa15..e4cead40b0 100644 --- a/apps/www/content/docs/api/core/store.mdx +++ b/apps/www/content/docs/api/core/store.mdx @@ -3,7 +3,7 @@ title: Store description: API reference for Plate store. --- -`Plate` is using jotai to store the state of the editor. +`Plate` is using [jotai](https://github.com/pmndrs/jotai) to store the state of the editor. **Note**: To use the store hooks in a component, it needs to be rendered @@ -32,9 +32,9 @@ A unique ID used as a provider scope. Use it if you have multiple `Plate` in the Function used to decorate ranges in the editor. -``ts +```ts (options: { editor: PlateEditor; entry: TNodeEntry }) => Range[] -`` +``` @@ -44,25 +44,25 @@ Whether `Editable` is rendered so slate DOM is resolvable. Controlled callback called when the editor state changes. -``ts +```ts (options: { editor: PlateEditor; value: ValueOf }) => void -`` +``` Controlled callback called when the editor.selection changes. -``ts +```ts (options: { editor: PlateEditor; selection: TSelection }) => void -`` +``` Controlled callback called when the editor.children changes. -``ts +```ts (options: { editor: PlateEditor; value: ValueOf }) => void -`` +``` @@ -115,14 +115,14 @@ The following store keys are exposed in `editor.setPlateState`: Use `usePlateSelectors(id).()` when you need to subscribe to a value. - **Example:** `const value = usePlateSelectors(id).value()` will subscribe to `value` changes. -- It's using useAtomValue under the hood. +- It's using [useAtomValue](https://jotai.org/docs/utils/use-atom-value) under the hood. ## Actions Use `usePlateStates(id).()` when you need both the value and the setter of a store property. - **Example:** `const [value, setValue] = usePlateStates(id).value()` -- It's using useAtom under the hood. +- It's using [useAtom](https://jotai.org/docs/basics/primitives#use-atom) under the hood. ### `useRedecorate` diff --git a/apps/www/content/docs/api/slate-react.mdx b/apps/www/content/docs/api/slate-react.mdx index cb45fbf4de..b3ef616505 100644 --- a/apps/www/content/docs/api/slate-react.mdx +++ b/apps/www/content/docs/api/slate-react.mdx @@ -7,7 +7,7 @@ description: API reference for @udecode/slate-react. ## React Editor -Find the corresponding documentation in the Slate React docs. +Find the corresponding documentation in the [Slate React docs](https://docs.slatejs.org/libraries/slate-react/react-editor). ### `SlateProps` diff --git a/apps/www/content/docs/api/slate.mdx b/apps/www/content/docs/api/slate.mdx index 003cfa603d..2b40cdf487 100644 --- a/apps/www/content/docs/api/slate.mdx +++ b/apps/www/content/docs/api/slate.mdx @@ -7,7 +7,7 @@ description: API reference for @udecode/slate. ## Editor -Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/transforms). ### `addMark` @@ -119,7 +119,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/nodes/element). ### `elementMatches` @@ -133,7 +133,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/libraries/slate-history). ### `isHistoryEditor` @@ -147,7 +147,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/nodes/node). ### `TDescendant` @@ -209,7 +209,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/locations/range). ### `isCollapsed` @@ -217,7 +217,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/nodes/text). ### `isText` @@ -231,7 +231,7 @@ Find the corresponding documentation in the Slate docs. +Find the corresponding documentation in the [Slate docs](https://docs.slatejs.org/api/transforms). ### `moveNodes` diff --git a/apps/www/content/docs/block-menu.mdx b/apps/www/content/docs/block-menu.mdx new file mode 100644 index 0000000000..2d4dc948a0 --- /dev/null +++ b/apps/www/content/docs/block-menu.mdx @@ -0,0 +1,137 @@ +--- +title: Block Menu +description: Provides quick access to block-specific actions. +docs: + - route: /docs/components/block-context-menu + title: Block Context Menu + + - route: https://pro.platejs.org/docs/components/block-menu + title: Block Menu + + - route: https://pro.platejs.org/docs/components/block-context-menu + title: Block Context Menu + +--- + + + + + +## Features + +- Right-click on a block to open the menu. +- Allows you to perform actions on blocks such as turn into, duplicating or deleting. +- Customizable menu items and actions. +- Keyboard navigation + + + +## Installation + +```bash +npm install @udecode/plate-selection @udecode/plate-node-id +``` + +## Usage + +```tsx +import { BlockMenuPlugin } from '@udecode/plate-selection/react'; +import { NodeIdPlugin } from '@udecode/plate-node-id'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + NodeIdPlugin, + BlockSelectionPlugin.configure({ + options: { + areaOptions: { + behaviour: { + scrolling: { + speedDivider: 1.5, + }, + startThreshold: 10, + }, + boundaries: '#scroll_container', + container: '#scroll_container', + selectables: '#scroll_container .slate-selectable', + selectionAreaClass: 'slate-selection-area', + }, + enableContextMenu: true, + }, + }), + BlockMenuPlugin.configure({ + render: { aboveEditable: BlockContextMenu }, + }), +] +``` + +- [BlockContextMenu](/docs/components/block-context-menu) + +To control the opening of the context menu for specific elements, you can use the `data-plate-open-context-menu` attribute: + +```tsx + + {children} + +``` + +Generally, we only need to prevent right-clicking on the padding of the ``. + +## Examples + +### Plate UI + +Refer to the preview above. + +### Plate Plus + +- Open the menu via the drag button or the three-dot menu on specific blocks (e.g. images) +- Includes a combobox that filters options as you type +- Supports nested menu options +- Advanced actions such as "Ask AI", colors, and commenting +- Beautifully crafted UI + + + +## Plugins + +### BlockMenuPlugin + +Block menu state management. + +## API + +### editor.api.blockMenu.hide + +Hides the block menu. + +### editor.api.blockMenu.show + +Shows the block menu for a specific block. + + + + The ID of the block to show the menu for. + + + The position to show the menu at. + + + +### editor.api.blockMenu.showContextMenu + +Shows the context menu for a specific block. + + + + The ID of the block to show the context menu for. + + + The position to show the context menu at. + + diff --git a/apps/www/content/docs/block-selection.mdx b/apps/www/content/docs/block-selection.mdx index 1dee154020..3f4c13ff23 100644 --- a/apps/www/content/docs/block-selection.mdx +++ b/apps/www/content/docs/block-selection.mdx @@ -3,7 +3,7 @@ title: Block Selection description: Select and manipulate entire text blocks. --- - + @@ -50,8 +50,9 @@ To control the scrollable container, configure the `boundaries` and `container` For this to work effectively: -1. Add an `id` or `className` to your scroll container. +1. Add an `id` or `className` to your scroll container.If you not sure about the container, you can add it to the `` component. 2. Use the appropriate selector in your configuration. +3. Don't forget to set `position: relative` to your scroll container. Example configuration: @@ -93,6 +94,46 @@ Example: /> ``` +### Set scroll speed + +useing `options.areaOptions.behaviour.scrolling.speedDivider` to set the scroll speed. + +The value of `1.5` is our recommended speed.Since it's same with the default speed of the browser. + + +```ts + areaOptions: { + behaviour: { + scrolling: { + speedDivider: 1.5, + }, + }, + }, +``` + +Full configuration recommended: + +```ts +options: { + areaOptions: { + behaviour: { + scrolling: { + speedDivider: 1.5, + }, + // The distance needed to move for the selection area to appear. + // If it’s too small, it may cause the mouse click event to be blocked. 30 is a good default. + startThreshold: 30, + }, + boundaries: '#your-scroll-container-id', + container: '#your-scroll-container-id', + selectables: '#your-scroll-container-id .slate-selectable', + selectionAreaClass: 'slate-selection-area', + }, + // if not using plate-ui context menu, set it to false + enableContextMenu: true, +}, +``` + ## Styling ### Selection area diff --git a/apps/www/content/docs/callout.mdx b/apps/www/content/docs/callout.mdx new file mode 100644 index 0000000000..f885712d01 --- /dev/null +++ b/apps/www/content/docs/callout.mdx @@ -0,0 +1,158 @@ +--- +title: Callout +description: Highlight important information or add special notes. +docs: + - route: https://pro.platejs.org/docs/components/callout-element + title: Callout Element +--- + + + + + +## Features + +- Customizable callout blocks for highlighting important information +- Support for different callout variants (e.g., info, warning, error) +- Ability to set custom icons or emojis for callouts + + + +## Installation + + +```bash +npm install @udecode/plate-callout +``` + +## Usage + +```tsx +import { CalloutPlugin } from '@udecode/plate-callout/react'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + CalloutPlugin, +] +``` + +```tsx +const components = { + // ...otherComponents, + [CalloutPlugin.key]: CalloutElement, +} +``` + +- [CalloutElement](https://pro.platejs.org/docs/components/callout-element) (Plus) + +## Examples + +### Plate UI + +Work in progress. + +### Plate Plus + +- Insert callouts using the slash command +- Ability to change the callout emoji +- Beautifully crafted UI + + + +## Plugins + +### CalloutPlugin + +Callout element plugin. + +## Transforms + +### editor.tf.insert.callout + +Inserts a callout element into the editor. + + + + The editor instance. + + + Options for inserting the callout. + + + The variant of the callout to insert. + + + Other options from `InsertNodesOptions`. + + + + + +## Hooks + +### useCalloutEmojiPicker + +This hook manages the emoji picker functionality for callouts. + + + + Options for the callout emoji picker. + + + Whether the emoji picker is open. + + + Function to set the open state of the emoji picker. + + + + + + + + Props for the emoji toolbar dropdown. + + + Whether the emoji picker is open. + + + Function to set the open state of the emoji picker, respecting read-only mode. + + + + + Props for the emoji picker component. + + + Whether the emoji picker is open. + + + Function to set the open state of the emoji picker. + + + Function called when an emoji is selected. It updates the callout's icon and closes the picker. + + + + + +## Types + +### TCalloutElement + +```typescript +interface TCalloutElement extends TElement { + variant?: string; + icon?: string; +} +``` diff --git a/apps/www/content/docs/collaboration.mdx b/apps/www/content/docs/collaboration.mdx index b0cf457ea1..3001f78b77 100644 --- a/apps/www/content/docs/collaboration.mdx +++ b/apps/www/content/docs/collaboration.mdx @@ -13,7 +13,7 @@ description: Collaborate with others in a single document. ## Features -- The yjs plugin enables support for collaboration using slate-yjs and Hocuspocus. +- The yjs plugin enables support for collaboration using [slate-yjs](https://docs.slate-yjs.dev/) and [Hocuspocus](https://docs.slate-yjs.dev/walkthroughs/collaboration-hocuspocus). @@ -45,7 +45,7 @@ const editor = createPlateEditor({ ## Backend -Follow the backend instructions in Hocuspocus docs. +Follow the backend instructions in [Hocuspocus docs](https://tiptap.dev/hocuspocus/getting-started). ## Plugins @@ -56,7 +56,7 @@ Follow the backend instructions in WithCursorsOptions API. +- [WithCursorsOptions API](https://docs.slate-yjs.dev/api/slate-yjs-core/cursor-plugin#withcursors). Configuration for the Hocuspocus provider. -- HocuspocusProviderConfiguration API. +- [HocuspocusProviderConfiguration API](https://tiptap.dev/hocuspocus/provider/configuration). Optional configuration for the Yjs integration. These options are passed to the `withYjs` function. -- WithYjsOptions API. +- [WithYjsOptions API](https://docs.slate-yjs.dev/api/slate-yjs-core/yjs-plugin#withyjs). @@ -102,7 +102,7 @@ Higher-order function that wraps a Plate editor with Yjs support, allowing for r -Extends `YjsEditorProps` and CursorEditor. +Extends `YjsEditorProps` and [CursorEditor](https://docs.slate-yjs.dev/api/slate-yjs-core/cursor-plugin#cursoreditor). @@ -122,8 +122,8 @@ The Hocuspocus provider instance. ### YHistoryEditorProps -Extends `YjsEditorProps`, YHistoryEditor. +Extends `YjsEditorProps`, [YHistoryEditor](https://docs.slate-yjs.dev/api/slate-yjs-core/history-plugin#yhistoryeditor). ### YjsEditorProps -YjsEditor API. \ No newline at end of file +[YjsEditor API](https://docs.slate-yjs.dev/api/slate-yjs-core/yjs-plugin#yjseditor). diff --git a/apps/www/content/docs/components/ai-leaf.mdx b/apps/www/content/docs/components/ai-leaf.mdx new file mode 100644 index 0000000000..c906b19bdd --- /dev/null +++ b/apps/www/content/docs/components/ai-leaf.mdx @@ -0,0 +1,77 @@ +--- +title: AI Leaf +description: Opens the AI menu from the editor toolbar. +component: true +docs: + - route: /docs/ai + title: AI + - route: https://pro.platejs.org/docs/components/ai-leaf + title: AI Leaf +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add ai-leaf -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [AI](/docs/ai) + + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + +## Plate Plus + +In Plate Plus, we provides more advanced styles and complete backend setup + +All of the backend setup is available in [Potion template](https://pro.platejs.org/docs/templates/potion). + + diff --git a/apps/www/content/docs/components/ai-menu.mdx b/apps/www/content/docs/components/ai-menu.mdx new file mode 100644 index 0000000000..957cfaffa1 --- /dev/null +++ b/apps/www/content/docs/components/ai-menu.mdx @@ -0,0 +1,87 @@ +--- +title: AI Menu +description: AI-powered menu for generating and inserting content in the editor. +component: true +docs: + - route: /docs/ai + title: AI + - route: https://pro.platejs.org/docs/components/ai-menu + title: AI Menu +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add ai-menu -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +```bash +npm install ai sonner +``` + +- [AI](/docs/ai) +- [Command](/docs/components/command) +- [Popover](/docs/components/popover) + + + + + + +Copy and paste the following code into your project. + + + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + + + +## Plate Plus + +In Plate Plus, we provides more advanced styles and complete backend setup + +All of the backend setup is available in [Potion template](https://pro.platejs.org/docs/templates/potion). + + diff --git a/apps/www/content/docs/components/ai-toolbar-button.mdx b/apps/www/content/docs/components/ai-toolbar-button.mdx new file mode 100644 index 0000000000..ca468a3dad --- /dev/null +++ b/apps/www/content/docs/components/ai-toolbar-button.mdx @@ -0,0 +1,78 @@ +--- +title: AI Toolbar Button +description: AI toolbar button +component: true +docs: + - route: /docs/ai + title: AI + - route: https://pro.platejs.org/docs/components/ai-toolbar-button + title: AI Toolbar Button +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add ai-toolbar-button -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [AI](/docs/ai) +- [Toolbar](/docs/components/toolbar) + + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + +## Plate Plus + +In Plate Plus, we provides more advanced styles and complete backend setup + +All of the backend setup is available in [Potion template](https://pro.platejs.org/docs/templates/potion). + + diff --git a/apps/www/content/docs/components/block-context-menu.mdx b/apps/www/content/docs/components/block-context-menu.mdx new file mode 100644 index 0000000000..34acb7b461 --- /dev/null +++ b/apps/www/content/docs/components/block-context-menu.mdx @@ -0,0 +1,85 @@ +--- +title: Block Context Menu +description: Display a context menu for blocks. +component: true +docs: + - route: /docs/block-menu + title: Block Menu + + - route: https://pro.platejs.org/docs/components/block-context-menu + title: Block Context Menu +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add block-context-menu -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +```bash +npm install @radix-ui/react-context-menu +``` +- [ContextMenu](/docs/components/context-menu) +- [BlockSelection](/docs/block-selection) +- [BlockMenu](/docs/block-menu) + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + +## Plus + +In Plate Plus, We have provided a more advanced menu. + +1. More advanced menu items. +2. Supports search functionality and carefully designed shortcuts. +3. More refined styles and animations. +4. You can open this menu in various ways, such as through the drag button. + + diff --git a/apps/www/content/docs/components/blockquote-element.mdx b/apps/www/content/docs/components/blockquote-element.mdx index b030cbb009..810ab525f2 100644 --- a/apps/www/content/docs/components/blockquote-element.mdx +++ b/apps/www/content/docs/components/blockquote-element.mdx @@ -5,6 +5,8 @@ component: true docs: - route: /docs/basic-elements title: Block Quote + - route: https://pro.platejs.org/docs/components/blockquote-element + title: Block Quote --- ## Installation @@ -60,3 +62,22 @@ Update the import paths to match your project setup. + +## Plus + +In Plate Plus, we have enhanced the block quote functionality: + +- Optimized quote styling for a more polished appearance +- Added the ability to convert any element into a block quote using: + - The toolbar + - The right-click context menu + - The plus button next to the DnD button. +- Improved user experience for working with block quotes + +These enhancements make it easier and more intuitive to use block quotes in your content. + + diff --git a/apps/www/content/docs/components/caption.mdx b/apps/www/content/docs/components/caption.mdx index 77a9915be6..836fffdcb1 100644 --- a/apps/www/content/docs/components/caption.mdx +++ b/apps/www/content/docs/components/caption.mdx @@ -5,6 +5,8 @@ component: true docs: - route: /docs/caption title: Caption + - route: https://pro.platejs.org/docs/components/media-toolbar + title: Media Toolbar --- ## Installation @@ -63,3 +65,18 @@ Update the import paths to match your project setup. + + +## Plus + +In Plate Plus, we have enhanced the caption user experience: + +Use the media toolbar to add captions when hovering over the image. + +Try it out by hovering over the image in the example below. + + diff --git a/apps/www/content/docs/components/changelog.mdx b/apps/www/content/docs/components/changelog.mdx index 20f9d7dd3d..7c07f1ee92 100644 --- a/apps/www/content/docs/components/changelog.mdx +++ b/apps/www/content/docs/components/changelog.mdx @@ -11,6 +11,23 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver ## October 2024 #15 +### October 26 #15.5 + +- Rename `indent-todo-marker-component` to `indent-todo-marker`. + +### October 25 #15.4 + +- `slash-input-element`: add AI command & add `focusEditor` option + +### October 24 #15.3 + +- Add `ai-menu`, `ai-menu-items`, `ai-toolbar-button`, `ai-chat-editor`, `block-context-menu`, `context-menu`, `ghost-text`, `toc-element` +- `command`: add `InputCommand` +- `toolbar` new style +- `editor` demo variant +- `heading-element`: remove `isFirstBlock` prop +- misc: `fixed-toolbar`, `fixed-toolbar-buttons`, `floating-toolbar`, `floating-toolbar-buttons`, `mode-dropdown-menu`, `turn-into-dropdown-menu`, `button`, `cursor-overlay`, `excalidraw-element`, `inline-combobox`, `slash-input-element` + ### October 14 #15.3 - New cli: `shadcx`. See [CLI](/docs/components/cli) and [components.json](/docs/components/components-json). diff --git a/apps/www/content/docs/components/cli.mdx b/apps/www/content/docs/components/cli.mdx index 1dca84cf63..8fe96d40c7 100644 --- a/apps/www/content/docs/components/cli.mdx +++ b/apps/www/content/docs/components/cli.mdx @@ -13,7 +13,7 @@ The `init` command installs dependencies, adds the `cn` util, configures `tailwi npx shadcx@latest init -u https://platejs.org/r -n plate-ui ``` -If you also want to use shadcn/ui in your project, you can use the following command: +If you also want to use [shadcn/ui](https://ui.shadcn.com/) in your project, you can use the following command: ```bash npx shadcx@latest init @@ -103,7 +103,7 @@ npx shadcx@latest add align-dropdown-menu -c ./apps/www ## Example components.json -Here's an example `components.json` file configured for shadcn/ui and Plate UI: +Here's an example `components.json` file configured for [shadcn/ui](https://ui.shadcn.com/) and Plate UI: ```json { diff --git a/apps/www/content/docs/components/code-block-element.mdx b/apps/www/content/docs/components/code-block-element.mdx index ab837ebde6..455986cf6d 100644 --- a/apps/www/content/docs/components/code-block-element.mdx +++ b/apps/www/content/docs/components/code-block-element.mdx @@ -65,3 +65,20 @@ Update the import paths to match your project setup. + +## Plus + +In Plate Plus, we've enhanced the code block user experience with two new features: + +1. A "Copy" button to easily copy the entire code snippet. +2. A "Block Menu" button to access additional options and actions. + +These buttons appear in the top right corner of the code block when you hover over it, providing a clean and intuitive interface. + +This improved functionality makes working with code blocks more efficient and user-friendly in Plate Plus. + + diff --git a/apps/www/content/docs/components/color-dropdown-menu.mdx b/apps/www/content/docs/components/color-dropdown-menu.mdx index 697360de59..e6418e71c0 100644 --- a/apps/www/content/docs/components/color-dropdown-menu.mdx +++ b/apps/www/content/docs/components/color-dropdown-menu.mdx @@ -79,3 +79,18 @@ Update the import paths to match your project setup. + +## Plus + +In Plate Plus, we've enhanced the color dropdown menu user experience with two new features: + +1. Text color can be modified using the floating toolbar or block menu, providing more flexibility in formatting. +2. An improved color picker interface with custom color options and a color input field for precise color selection. + +These enhancements make it easier and more intuitive to customize text colors in your content. + + diff --git a/apps/www/content/docs/components/column-group-element.mdx b/apps/www/content/docs/components/column-group-element.mdx index 21075c1560..8ad634b1e4 100644 --- a/apps/www/content/docs/components/column-group-element.mdx +++ b/apps/www/content/docs/components/column-group-element.mdx @@ -46,7 +46,11 @@ Copy and paste the following code into your project. - + + diff --git a/apps/www/content/docs/components/comment-leaf.mdx b/apps/www/content/docs/components/comment-leaf.mdx index bd3b0cca02..ac2dcd8e8d 100644 --- a/apps/www/content/docs/components/comment-leaf.mdx +++ b/apps/www/content/docs/components/comment-leaf.mdx @@ -60,3 +60,14 @@ Update the import paths to match your project setup. + + +## Plus + +In Plate Plus, we provide more user-friendly styles and a complete **backend** design + + diff --git a/apps/www/content/docs/components/comment-toolbar-button.mdx b/apps/www/content/docs/components/comment-toolbar-button.mdx index bfc268e7c8..df4f3520df 100644 --- a/apps/www/content/docs/components/comment-toolbar-button.mdx +++ b/apps/www/content/docs/components/comment-toolbar-button.mdx @@ -64,3 +64,13 @@ Update the import paths to match your project setup. + +## Plus + +In Plate Plus, we provide more user-friendly styles and a complete **backend** design + + diff --git a/apps/www/content/docs/components/comments-popover.mdx b/apps/www/content/docs/components/comments-popover.mdx index 9c8ac6b1da..e7beabde81 100644 --- a/apps/www/content/docs/components/comments-popover.mdx +++ b/apps/www/content/docs/components/comments-popover.mdx @@ -78,3 +78,14 @@ Update the import paths to match your project setup. + + +## Plus + +In Plate Plus, we provide more user-friendly styles and a complete **backend** design + + diff --git a/apps/www/content/docs/components/components-json.mdx b/apps/www/content/docs/components/components-json.mdx index b57dbd7857..27c9357f63 100644 --- a/apps/www/content/docs/components/components-json.mdx +++ b/apps/www/content/docs/components/components-json.mdx @@ -21,7 +21,7 @@ npx shadcx@latest init For Plate UI components, you have two options: -1. Initialize both shadcn/ui and Plate UI: +1. Initialize both [shadcn/ui](https://ui.shadcn.com/) and Plate UI: ```bash npx shadcx@latest init npx shadcx@latest init -u https://platejs.org/r -n plate-ui @@ -32,7 +32,7 @@ For Plate UI components, you have two options: npx shadcx@latest init -u https://platejs.org/r -n plate-ui ``` -See the CLI section for more information. +See the [CLI section](/docs/cli) for more information. ## $schema @@ -48,7 +48,7 @@ You can see the JSON Schema for `components.json` [here](https://ui.shadcn.com/s Configuration to help the CLI understand how Tailwind CSS is set up in your project. -See the installation section for how to set up Tailwind CSS. +See the [installation section](/docs/components/installation) for how to set up Tailwind CSS. ### tailwind.config @@ -100,7 +100,7 @@ To use utility classes for theming set `tailwind.cssVariables` to `false`. For C } ``` -For more information, see the theming docs. +For more information, see the [theming docs](/docs/components/theming). **This cannot be changed after initialization.** To switch between CSS variables and utility classes, you'll have to delete and re-install your components. @@ -191,7 +191,7 @@ Import alias for `hooks` such as `use-media-query` or `use-toast`. ## registries -The `registries` section allows you to configure multiple component registries for your project. This is particularly useful when working with Plate UI components alongside the shadcn/ui components. +The `registries` section allows you to configure multiple component registries for your project. This is particularly useful when working with Plate UI components alongside the [shadcn/ui](https://ui.shadcn.com/) components. ```json title="components.json" { diff --git a/apps/www/content/docs/components/context-menu.mdx b/apps/www/content/docs/components/context-menu.mdx new file mode 100644 index 0000000000..ebcb89cde2 --- /dev/null +++ b/apps/www/content/docs/components/context-menu.mdx @@ -0,0 +1,69 @@ +--- +title: Context Menu +description: Displays a menu to the user — such as a set of actions or functions — triggered by a button. +component: true +links: + doc: https://www.radix-ui.com/docs/primitives/components/context-menu + api: https://www.radix-ui.com/docs/primitives/components/context-menu#api-reference +--- + +## Installation + + + + + CLI + Manual + + + +```bash +npx shadcn@latest add context-menu -r plate-ui +``` + + + + + + + +Install the following dependencies: + +```bash +npm install @radix-ui/react-context-menu +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@/components/ui/context-menu" +``` + +```tsx + + Right click + + Profile + Billing + Team + Subscription + + +``` diff --git a/apps/www/content/docs/components/draggable.mdx b/apps/www/content/docs/components/draggable.mdx index 94118b952e..d830297a89 100644 --- a/apps/www/content/docs/components/draggable.mdx +++ b/apps/www/content/docs/components/draggable.mdx @@ -106,4 +106,15 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; ## Examples - \ No newline at end of file + + + +## Plus + +In Plate Plus, we provide more user-friendly styles and you can open [block menu](/docs/block-menu) by clicking on the draggable button. + + diff --git a/apps/www/content/docs/components/floating-toolbar-buttons.mdx b/apps/www/content/docs/components/floating-toolbar-buttons.mdx index 52f069bf84..8aa99bbc0e 100644 --- a/apps/www/content/docs/components/floating-toolbar-buttons.mdx +++ b/apps/www/content/docs/components/floating-toolbar-buttons.mdx @@ -11,6 +11,8 @@ docs: title: More Dropdown Menu - route: /docs/components/turn-into-dropdown-menu title: Turn Into Dropdown Menu + - route: https://pro.platejs.org/docs/components/ai-toolbar-button + title: Toolbar --- ## Installation @@ -69,3 +71,12 @@ Update the import paths to match your project setup. + + +### Plus + +In Plate Plus, we have designed beautiful animations and more comprehensive buttons for the floating toolbar. + +Try it out by selecting some text in the below editor. + + \ No newline at end of file diff --git a/apps/www/content/docs/components/floating-toolbar.mdx b/apps/www/content/docs/components/floating-toolbar.mdx index 6dabb6ce93..e048bdfc6f 100644 --- a/apps/www/content/docs/components/floating-toolbar.mdx +++ b/apps/www/content/docs/components/floating-toolbar.mdx @@ -5,6 +5,8 @@ component: true docs: - route: /docs/components/toolbar title: Toolbar + - route: https://pro.platejs.org/docs/components/ai-toolbar-button + title: Toolbar --- ## Installation @@ -61,6 +63,20 @@ Update the import paths to match your project setup. ## Examples +### Basic + + +### Plus + +In Plate Plus, we have designed beautiful animations and more comprehensive buttons for the floating toolbar. + +Try it out by selecting some text in the below editor. + + diff --git a/apps/www/content/docs/components/ghost-text.mdx b/apps/www/content/docs/components/ghost-text.mdx new file mode 100644 index 0000000000..bf5842c42b --- /dev/null +++ b/apps/www/content/docs/components/ghost-text.mdx @@ -0,0 +1,80 @@ +--- +title: Ghost Text +description: Display AI-generated suggestions in a hover card. +component: true +docs: + - route: /docs/copilot + title: Copilot + - route: https://pro.platejs.org/docs/components/ghost-text + title: Ghost Text +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add ghost-text -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [Copilot](/docs/copilot) + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + +## Plate Plus + +In Plate Plus, we provides more advanced styles and complete backend setup + +1. Hover card: a new style of hover card that is more user-friendly. You can **hover** over the ghost text to see the hover card. +2. Marks: support for marks like bold, italic, underline, etc.This means you can see bold text and **links** in the ghost text +3. Backend: complete backend setup. + +All of the backend setup is available in [Potion template](https://pro.platejs.org/docs/templates/potion). + + diff --git a/apps/www/content/docs/components/image-element.mdx b/apps/www/content/docs/components/image-element.mdx index 55df2810c0..42d29d8d8c 100644 --- a/apps/www/content/docs/components/image-element.mdx +++ b/apps/www/content/docs/components/image-element.mdx @@ -69,3 +69,15 @@ Update the import paths to match your project setup. + + +## Plus + +In Plate Plus, we have significantly enhanced the image user experience with the following features: + +1. **Media Toolbar**: A convenient toolbar appears when hovering over an image, providing quick access to editing options. +2. **Full-Stack Backend Integration**: Seamlessly handle image uploads, storage, and retrieval with our robust backend integration. +3. **Image preview**: double click to see the image preview. +4. **Download image**: download the image to your local machine by media toolbar. + + \ No newline at end of file diff --git a/apps/www/content/docs/components/indent-todo-marker.mdx b/apps/www/content/docs/components/indent-todo-marker.mdx new file mode 100644 index 0000000000..d1f78ff151 --- /dev/null +++ b/apps/www/content/docs/components/indent-todo-marker.mdx @@ -0,0 +1,64 @@ +--- +title: Indent Todo Marker +description: Turn any block into a todo list item. +component: true +docs: + - route: /docs/indent-list + title: Indent List + - route: https://pro.platejs.org/docs/components/indent-todo-marker + title: Indent Todo Marker +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add indent-todo-marker -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [Indent List](/docs/indent-list) + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + diff --git a/apps/www/content/docs/components/index.mdx b/apps/www/content/docs/components/index.mdx index d37e77e1bd..5559acf676 100644 --- a/apps/www/content/docs/components/index.mdx +++ b/apps/www/content/docs/components/index.mdx @@ -25,12 +25,12 @@ Is it a replacement to shadcn/ui? -No, it's not a replacement to shadcn/ui. -Instead, Plate UI an extended collection of components built on top of shadcn/ui and Radix UI, specifically designed for **rich-text editor** design. For other parts of your app, you should continue to use other components such as shadcn/ui. +No, it's not a replacement to [shadcn/ui](https://ui.shadcn.com/). +Instead, Plate UI an extended collection of components built on top of [shadcn/ui](https://ui.shadcn.com/) and [Radix UI](https://www.radix-ui.com/), specifically designed for **rich-text editor** design. For other parts of your app, you should continue to use other components such as [shadcn/ui](https://ui.shadcn.com/). -Plate UI adheres to the same conventions, usage, and patterns as shadcn/ui, meaning you can seamlessly integrate it with your existing shadcn/ui components rather than replacing them. +Plate UI adheres to the same conventions, usage, and patterns as [shadcn/ui](https://ui.shadcn.com/), meaning you can seamlessly integrate it with your existing [shadcn/ui](https://ui.shadcn.com/) components rather than replacing them. -Next to shadcn/ui, Plate UI is the first published component registry. +Next to [shadcn/ui](https://ui.shadcn.com/), Plate UI is the first published component registry. @@ -75,7 +75,7 @@ Which frameworks are supported? Can I use this in my project? -Yes. Free to use for personal and commercial projects. No attribution required, but sharing your editor app here is appreciated! +Yes. Free to use for personal and commercial projects. No attribution required, but sharing your editor app [here](https://github.com/udecode/plate/issues/818) is appreciated! @@ -85,8 +85,8 @@ Yes. Free to use for personal and commercial projects. No attribution required, ## Credits -- shadcn/ui - For the UI, the docs and the CLI. -- Radix UI - For the primitives. -- Vercel - For hosting. -- Shu Ding - The typography style is adapted from his work on Nextra. -- cmdk - For the `` component. +- [shadcn/ui](https://ui.shadcn.com/) - For the UI, the docs and the CLI. +- [Radix UI](https://radix-ui.com) - For the primitives. +- [Vercel](https://vercel.com) - For hosting. +- [Shu Ding](https://shud.in) - The typography style is adapted from his work on Nextra. +- [cmdk](https://cmdk.paco.me) - For the `` component. diff --git a/apps/www/content/docs/components/insert-dropdown-menu.mdx b/apps/www/content/docs/components/insert-dropdown-menu.mdx index 7e68f99548..3b6b7030d0 100644 --- a/apps/www/content/docs/components/insert-dropdown-menu.mdx +++ b/apps/www/content/docs/components/insert-dropdown-menu.mdx @@ -72,3 +72,11 @@ Update the import paths to match your project setup. + +## Plus + +In Plate Plus, we provide a new way to insert blocks. + +Click the plus button next to the drag button + + \ No newline at end of file diff --git a/apps/www/content/docs/components/installation/manual.mdx b/apps/www/content/docs/components/installation/manual.mdx index d9d57ed88e..2d364a26d6 100644 --- a/apps/www/content/docs/components/installation/manual.mdx +++ b/apps/www/content/docs/components/installation/manual.mdx @@ -9,7 +9,7 @@ description: Add dependencies to your project manually. Components are styled using Tailwind CSS. You need to install Tailwind CSS in your project. -Follow the Tailwind CSS installation instructions to get started. +[Follow the Tailwind CSS installation instructions](https://tailwindcss.com/docs/installation) to get started. ### Add dependencies @@ -43,7 +43,7 @@ Add the icons you'll use in `components/icons.tsx`: -We use icons from Lucide. You can use any icon library you want. +We use icons from [Lucide](https://lucide.dev). You can use any icon library you want. ### Add components diff --git a/apps/www/content/docs/components/installation/next.mdx b/apps/www/content/docs/components/installation/next.mdx index 94edf8882a..e1e8bacfcd 100644 --- a/apps/www/content/docs/components/installation/next.mdx +++ b/apps/www/content/docs/components/installation/next.mdx @@ -5,7 +5,7 @@ description: Install and configure Next.js. -A template is available to help you get started quickly. +A [template](https://github.com/udecode/plate/tree/main/templates/plate-template) is available to help you get started quickly. @@ -19,7 +19,7 @@ Run the `init` command to create a new Next.js project or to setup an existing o npx shadcx@latest init -u https://platejs.org/r -n plate-ui ``` -If you want to use shadcn/ui combined with Plate UI, run instead: +If you want to use [shadcn/ui](https://ui.shadcn.com/) combined with Plate UI, run instead: ```bash npx shadcx@latest init @@ -41,7 +41,7 @@ Add the icons you'll use in `components/icons.tsx`: -We use icons from Lucide. You can use any icon library you want. +We use icons from [Lucide](https://lucide.dev). You can use any icon library you want. ### Add components diff --git a/apps/www/content/docs/components/installation/vite.mdx b/apps/www/content/docs/components/installation/vite.mdx index 43a48430fb..5728e76a15 100644 --- a/apps/www/content/docs/components/installation/vite.mdx +++ b/apps/www/content/docs/components/installation/vite.mdx @@ -115,7 +115,7 @@ Add the icons you'll use in `components/icons.tsx`: -We use icons from Lucide. You can use any icon library you want. +We use icons from [Lucide](https://lucide.dev). You can use any icon library you want. ### Add components diff --git a/apps/www/content/docs/components/slash-input-element.mdx b/apps/www/content/docs/components/slash-input-element.mdx new file mode 100644 index 0000000000..643782ceef --- /dev/null +++ b/apps/www/content/docs/components/slash-input-element.mdx @@ -0,0 +1,93 @@ +--- +title: Slash Input Element +description: Allows you to insert various elements into your editor using a slash command. +component: true +docs: + - route: /docs/slash-command + title: Slash Command + - route: https://pro.platejs.org/docs/components/slash-input-element + title: Slash Input Element +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add slash-input-element -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [Combobox](/docs/combobox) +- [Slash Command](/docs/slash-command) +- [InlineCombobox](/docs/components/inline-combobox) + + + + + +Copy and paste the following code into your project. + + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + + + +## Plate Plus + +We offer an enhanced user interface design and a more comprehensive set of options, including premium plugins such as Math Callout and Media Upload. +This provides a more robust and feature-rich editing experience for users who require advanced functionality. + +Some key improvements in Plate Plus include: + +- Refined UI design for better usability and aesthetics +- Extended set of slash menu options +- Integration of premium plugins like Math Upload for specialized editing needs +- No need to worry about the focus issue mentioned above. +- Support grouping and Carefully selected keyword. +- Trigger slash menu by click the puls button on the left of the paragraph. + +You can try typing /``` to quickly insert a code block. + +Feel free to experiment with different commands to see how the slash menu enhances your editing experience! + + diff --git a/apps/www/content/docs/components/toc-element.mdx b/apps/www/content/docs/components/toc-element.mdx new file mode 100644 index 0000000000..fefda5d33c --- /dev/null +++ b/apps/www/content/docs/components/toc-element.mdx @@ -0,0 +1,81 @@ +--- +title: TOC Element +description: A table of contents element for displaying hierarchical document structure. +component: true +docs: + - route: /docs/toc + title: Table of Contents + - route: https://pro.platejs.org/docs/components/toc-sidebar + title: TOC Sidebar +--- + +## Installation + + + + +CLI +Manual + + + +```bash +npx @udecode/plate-ui@latest add toc-element -r plate-ui +``` + + + + + + + + + +Install the following dependencies: + +- [Heading](/docs/toc) + + + + + + + +Copy and paste the following code into your project. + + + + + + + +Update the import paths to match your project setup. + + + + + + + + + +## Examples + + + + + +## Plate Plus +The TocSideBar component in Plate Plus offers the following features: + +- Responsive design that adapts to different screen sizes +- Dynamic highlighting of the corresponding thumbnail on the right side based on the current section +- Hover thumbnail to see the preview of the section with smooth animation +- Elegant transition effects when navigating between sections +- Animated highlighting of the current section in the sidebar + +Here's an example of how to use the enhanced TocSideBar component in Plate Plus: + + + + diff --git a/apps/www/content/docs/components/turn-into-dropdown-menu.mdx b/apps/www/content/docs/components/turn-into-dropdown-menu.mdx index 9d5fac3507..8649f0f048 100644 --- a/apps/www/content/docs/components/turn-into-dropdown-menu.mdx +++ b/apps/www/content/docs/components/turn-into-dropdown-menu.mdx @@ -73,3 +73,12 @@ Update the import paths to match your project setup. + + +## Plus + +In Plate Plus, we have optimized the styles and added beautiful animations. + +Try it out by selecting some text in the below editor. + + \ No newline at end of file diff --git a/apps/www/content/docs/copilot.mdx b/apps/www/content/docs/copilot.mdx new file mode 100644 index 0000000000..78a95a6b49 --- /dev/null +++ b/apps/www/content/docs/copilot.mdx @@ -0,0 +1,233 @@ +--- +title: Copilot +description: Render AI suggestions ghost text as you type. +docs: + - route: /docs/components/ghost-text + title: Ghost Text + - route: https://pro.platejs.org/docs/components/ghost-text + title: Ghost Text +--- + + + + + +## Features + +- Renders ghost text suggestions as you type +- Two trigger modes: + - Shortcut (`Ctrl+Space`). Press again for alternative suggestions. + - Debounce mode: automatically triggers after a space at paragraph ends +- Accept suggestions with Tab or word-by-word with `Cmd+→` +- Built-in support for Vercel AI SDK completion API + + + +## Installation + +```bash +npm install @udecode/plate-ai @udecode/plate-markdown +``` + +## Usage + +```tsx +import { CopilotPlugin } from '@udecode/plate-ai/react'; +import { + MarkdownPlugin, + serializeMdNodes, + stripMarkdown, +} from '@udecode/plate-markdown'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + MarkdownPlugin.configure({ options: { indentList: true } }), + CopilotPlugin.configure(({ api }) => ({ + options: { + completeOptions: { + api: '/api/your-api-endpoint', + body: { + system: `You are an advanced AI writing assistant, similar to VSCode Copilot but for general text. Your task is to predict and generate the next part of the text based on the given context. + +Rules: +- Continue the text naturally up to the next punctuation mark (., ,, ;, :, ?, or !). +- Maintain style and tone. Don't repeat given text. +- For unclear context, provide the most likely continuation. +- Handle code snippets, lists, or structured text if needed. +- Don't include """ in your response. +- CRITICAL: Always end with a punctuation mark. +- CRITICAL: Avoid starting a new block. Do not use block formatting like >, #, 1., 2., -, etc. The suggestion should continue in the same block as the context. +- If no context is provided or you can't generate a continuation, return "0" without explanation.`, + }, + onFinish: (_, completion) => { + if (completion === '0') return; + + api.copilot.setBlockSuggestion({ + //stripMarkdownBlocks in plus GhostText + text: stripMarkdown(completion), + }); + }, + }, + debounceDelay: 500, + getPrompt: ({ editor }) => { + const contextEntry = getAncestorNode(editor); + + if (!contextEntry) return ''; + + const prompt = serializeMdNodes([contextEntry[0] as TElement]); + + return `Continue the text up to the next punctuation mark: +""" +${prompt} +"""`; + }, + renderGhostText: GhostText, + }, +})); +``` + +- [GhostText](/docs/components/ghost-text) + +## Tab Key Handling + +The Copilot plugin uses the Tab key to accept suggestions. To avoid conflicts with other plugins that use Tab (like `IndentPlugin` or `TabbablePlugin`), ensure `CopilotPlugin` is placed before them in your plugin configuration. This allows Copilot to handle Tab key events first when suggestions are present. + +```tsx +const plugins = [ + // ...otherPlugins, + CopilotPlugin, + // Place tab-using plugins after Copilot + IndentPlugin, + TabbablePlugin, +]; +``` + +## Examples + +### Plate UI + +Refer to the preview above. + +### Plate Plus + +- Automatic AI inline suggestions powered by AI SDK (OpenAI). Code available in [Potion template](https://pro.platejs.org/docs/templates/potion) +- Rich text suggestions including marks and links +- Hover card with additional information +- Beautifully crafted UI + + + +## Keyboard Shortcuts + + + + Trigger suggestion. Press again for alternative suggestions. + + Accept the entire suggestion. + + Accept the next word of the suggestion. + + Dismiss the current suggestion. + + +## Plugins + +### CopilotPlugin + + + + Additional conditions to auto trigger copilot. Default checks: + - Block above is not empty + - Block above ends with a space + - No existing suggestion + + + + AI completion configuration options. See [AI SDK useCompletion + Parameters](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-completion#parameters). + + + + Delay for debouncing auto-triggered suggestions. **Default:** `0` + + + + Function to extract the next word from suggestion text. + + + + Function to generate the prompt for AI completion. **Default:** Uses markdown + serialization of ancestor node + + + + Component to render ghost text suggestions. + + + + Conditions to trigger copilot. Default checks: + - Selection is not expanded + - Selection is at block end + + + +## API + +### editor.api.copilot.accept() + +Accepts the current suggestion. + +### editor.api.copilot.acceptNextWord() + +Accepts the next word of the suggestion. + +### editor.api.copilot.reset() + +Resets the plugin state: + +- Aborts any ongoing API request +- Clears the current completion +- Clears suggestion node ID and text + +### editor.api.copilot.setBlockSuggestion() + +Sets suggestion text for a block. + + + + + + The suggestion text to set. + + + Target block ID. Defaults to current block if not provided. + + + + + +### editor.api.copilot.stop() + +Stops ongoing suggestion requests: + +- Cancels debounced trigger calls +- Aborts current API request +- Resets abort controller + +### editor.api.copilot.triggerSuggestion() + +Triggers a new suggestion request. Can be debounced based on plugin configuration. diff --git a/apps/www/content/docs/csv.mdx b/apps/www/content/docs/csv.mdx index 9747325f73..d3e2dea22c 100644 --- a/apps/www/content/docs/csv.mdx +++ b/apps/www/content/docs/csv.mdx @@ -55,7 +55,7 @@ Options to be passed to the PapaParse library for parsing CSV data. - **Default:** **`{ header: true }`** (Indicating that the first row of the CSV data should be treated as a header.) -Refer to PapaParse documentation for more details about these options. +Refer to [PapaParse documentation](https://www.papaparse.com/docs#config) for more details about these options. @@ -85,7 +85,7 @@ Options to be passed to the PapaParse library for parsing CSV data. - **Default:** **`{ header: true }`** (Indicating that the first row of the CSV data should be treated as a header.) -Refer to PapaParse documentation for more details about these options. +Refer to [PapaParse documentation](https://www.papaparse.com/docs#config) for more details about these options. @@ -100,4 +100,4 @@ Refer to PapaParse documentat This function creates a table representation of the CSV data. If the CSV file includes headers, they are added as the first row of the table. Each subsequent row represents the values from the CSV file. If the CSV file does not include headers, each row of data is represented as a row in the table. -Please note that the function assumes the usage of specific plugins corresponding to `ParagraphPlugin.key`, `TablePlugin.key`, `TableCellHeaderPlugin.key`, `TableRowPlugin.key`, and `TableCellPlugin.key`. \ No newline at end of file +Please note that the function assumes the usage of specific plugins corresponding to `ParagraphPlugin.key`, `TablePlugin.key`, `TableCellHeaderPlugin.key`, `TableRowPlugin.key`, and `TableCellPlugin.key`. diff --git a/apps/www/content/docs/debugging.mdx b/apps/www/content/docs/debugging.mdx index 98188d5b9d..353b2a1baf 100644 --- a/apps/www/content/docs/debugging.mdx +++ b/apps/www/content/docs/debugging.mdx @@ -169,9 +169,9 @@ Set breakpoints in your code using browser DevTools: If you're facing a complex issue: -1. Pick a [template](docs/getting-started). +1. Pick a [template](/docs/getting-started). 2. Add only the essential plugins and components to reproduce the issue. -3. If the issue persists, open an issue on GitHub or share your example on Discord. +3. If the issue persists, [open an issue on GitHub](https://github.com/udecode/plate/issues/new?assignees=&labels=bug&projects=&template=bug.yml) or share your example on [Discord](https://discord.gg/mAZRuBzGM3). ## Debug Error Types diff --git a/apps/www/content/docs/editor-methods.mdx b/apps/www/content/docs/editor-methods.mdx index 4e9fe08063..c052812a38 100644 --- a/apps/www/content/docs/editor-methods.mdx +++ b/apps/www/content/docs/editor-methods.mdx @@ -191,7 +191,7 @@ editor.setOptions(FindReplacePlugin, (draft) => { ### getOptionsStore -Get the zustand-x options store for a plugin: +Get the [zustand-x](https://github.com/udecode/zustand-x) options store for a plugin: ```ts const store = editor.getOptionsStore(FindReplacePlugin); @@ -223,7 +223,7 @@ const MyComponent = () => { ### useOptionsStore -Get the zustand-x store hook for a plugin: +Get the [zustand-x](https://github.com/udecode/zustand-x) store hook for a plugin: ```ts const store = editor.useOptionsStore(FindReplacePlugin); @@ -237,4 +237,4 @@ Update the global Plate state: ```ts editor.setPlateState('readOnly', true); -``` \ No newline at end of file +``` diff --git a/apps/www/content/docs/equation.mdx b/apps/www/content/docs/equation.mdx new file mode 100644 index 0000000000..24460a757d --- /dev/null +++ b/apps/www/content/docs/equation.mdx @@ -0,0 +1,122 @@ +--- +title: Equation +description: Enables the insertion and rendering of LaTeX equations in your editor. +docs: + - route: https://pro.platejs.org/docs/components/equation-element + title: Equation Element + - route: https://pro.platejs.org/docs/components/inline-equation-element + title: Inline Equation Element + - route: https://pro.platejs.org/docs/components/inline-equation-toolbar-button + title: Inline Equation Toolbar Button +--- + + + +## Features + +- LaTeX syntax support for complex mathematical expressions +- Both inline and block equation formats +- Real-time rendering of equations using KaTeX +- Easy editing and navigation within equations + + + +## Installation + +```bash +npm install @udecode/plate-math +``` + +## Usage + +```tsx +import { EquationPlugin, InlineEquationPlugin } from '@udecode/plate-math/react'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + EquationPlugin, + InlineEquationPlugin, +] +``` + +```tsx +const components = { + // ...otherComponents, + [EquationPlugin.key]: EquationElement, + [InlineEquationPlugin.key]: InlineEquationElement, +} +``` + +- [EquationElement](https://pro.platejs.org/docs/components/equation-element) (Plus) +- [InlineEquationElement](https://pro.platejs.org/docs/components/inline-equation-element) (Plus) + + +{/* ### How to use + +1. Use the slash command `/equation` to insert a block equation. +2. Use the slash command `/inline equation` to insert an inline equation. +3. Click on any equation to edit it. Press Escape to close the editor without saving changes. +4. Navigate through the equation using arrow keys. */} + +## Examples + +### Plate UI + +Work in progress. + +### Plate Plus + +- Mark text as equation from the toolbar +- Insert equation from slash command +- Beautifully crafted UI + + + +## Plugins + +### EquationPlugin + +Block equation element plugin. + +### InlineEquationPlugin + +Inline equation element plugin. + +## Transforms + +### editor.tf.insert.equation + +Inserts an empty block equation. + + + Options for the insert nodes transform. + + + +### editor.tf.insert.inlineEquation + +Inserts an inline equation. + + + The LaTeX expression to insert. If not provided, an empty equation will be inserted. + + + Options for the insert nodes transform. + + + +## Types + +### TEquationElement + +```typescript +interface TEquationElement extends TElement { + texExpression: string; +} +``` \ No newline at end of file diff --git a/apps/www/content/docs/examples/version-history.mdx b/apps/www/content/docs/examples/version-history.mdx index 9ef6ee80f2..8d58397a50 100644 --- a/apps/www/content/docs/examples/version-history.mdx +++ b/apps/www/content/docs/examples/version-history.mdx @@ -3,4 +3,25 @@ title: Version History description: Show a diff of two different points in a Plate document's history. --- + +## Examples + +### Plate UI + + +### Potion + +- Full version history: + - Save document snapshots + - View all document versions + - Compare any two versions + - Restore previous versions +- Visual diff comparison: + - Added content in green + - Removed content in red +- Beautifully crafted UI + +Try it out. Sign in, then click on the top right clock icon: + + \ No newline at end of file diff --git a/apps/www/content/docs/getting-started.mdx b/apps/www/content/docs/getting-started.mdx index d921ef0562..b3e7c68e4b 100644 --- a/apps/www/content/docs/getting-started.mdx +++ b/apps/www/content/docs/getting-started.mdx @@ -4,7 +4,7 @@ description: A quick tutorial to get you up and running with Plate. --- - Use our interactive builder to generate _personalized_ installation steps. + Use our [interactive builder](/?builder=true) to generate _personalized_ installation steps. @@ -13,11 +13,12 @@ description: A quick tutorial to get you up and running with Plate. You can choose one of the following templates to get started: -| Option | NextJS | Tailwind | Plate | Plugins | -| ------------------------------------------------------------------------------------------------------------------ | ------ | -------- | ----- | ------- | -| Plate playground template | ✅ | ✅ | ✅ | ✅ | -| Plate minimal template | ✅ | ✅ | ✅ | | -| NextJS template | ✅ | ✅ | | | +| Option | NextJS | Plate | Plugins | AI & Backend | +| --------------------------------------------------------------------------------- | ------ | ----- | ------- | ------------ | +| [Notion clone template](https://pro.platejs.org/docs/templates/potion) | ✅ | ✅ | ✅ | ✅ | +| [Plate playground template](https://github.com/udecode/plate-playground-template) | ✅ | ✅ | ✅ | | +| [Plate minimal template](https://github.com/udecode/plate-template) | ✅ | ✅ | | | +| [NextJS template](/docs/components/installation/next) | ✅ | | | | For an existing project, jump to the next step. @@ -39,8 +40,12 @@ npm install @udecode/plate slate slate-react slate-history slate-hyperscript rea Let's start with a minimal editor setup. -```tsx showLineNumbers {4,7-9} -import { usePlateEditor, Plate, PlateContent } from '@udecode/plate-common/react'; +```tsx showLineNumbers {1-5,11-13} +import { + usePlateEditor, + Plate, + PlateContent, +} from '@udecode/plate-common/react'; export default function BasicEditor() { const editor = usePlateEditor(); @@ -64,7 +69,10 @@ Let's give our editor some styles: [Editor](/docs/components/editor) is a styled - **Note**: `Editor` is just an example of a styled editor using Tailwind, and if you're using it, make sure to follow the installation steps in the [Manual Installation](/docs/components/installation/manual) guide. You can create your own styled version of `PlateContent`. + **Note**: `Editor` is just an example of a styled editor using Tailwind, and + if you're using it, make sure to follow the installation steps in the [Manual + Installation](/docs/components/installation/manual) guide. You can create your + own styled version of `PlateContent`. @@ -139,15 +147,20 @@ export default function BasicEditor() { ### Plugins - Use our interactive builder to pick your plugins. + Use our [interactive builder](/?builder=true) to pick your plugins. Let's use the basic plugins for a rich-text editor. -```tsx showLineNumbers {15-23} +```tsx showLineNumbers {20-28} // ... -import { BoldPlugin, ItalicPlugin, UnderlinePlugin, CodePlugin } from '@udecode/plate-basic-marks/react'; +import { + BoldPlugin, + ItalicPlugin, + UnderlinePlugin, + CodePlugin, +} from '@udecode/plate-basic-marks/react'; import { HeadingPlugin } from '@udecode/plate-heading/react'; import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; @@ -184,7 +197,8 @@ The plugins are functioning correctly. However, since we haven't specified any c **Note**: You don't need to add core plugins such as **`ReactPlugin`**, - **`HistoryPlugin`** and **`ParagraphPlugin`** as **`usePlateEditor`** already does it. + **`HistoryPlugin`** and **`ParagraphPlugin`** as **`usePlateEditor`** already + does it. ### Components @@ -192,7 +206,7 @@ The plugins are functioning correctly. However, since we haven't specified any c **Note**: Plate plugins are packaged unstyled, implying that you have complete control over markup and styling, hence you can integrate your own design - system or [Plate UI](/docs/components). If using the latter, use our interactive builder to pick your components. + system or [Plate UI](/docs/components). If using the latter, use our [interactive builder](/?builder=true) to pick your components. To plug-in all the components in one place, we can use the **`override.components`** option in **`usePlateEditor`**: @@ -246,10 +260,7 @@ const htmlValue = '

This is bold and italic text!

'; export default function BasicEditor() { const editor = usePlateEditor({ value: htmlValue, - plugins: [ - BoldPlugin, - ItalicPlugin, - ], + plugins: [BoldPlugin, ItalicPlugin], }); return ( @@ -262,6 +273,6 @@ export default function BasicEditor() { ### That's it! -You can now play around with the Playground and start building your own editor. +You can now play around with the [Playground](/#playground) and start building your own editor.
diff --git a/apps/www/content/docs/index.mdx b/apps/www/content/docs/index.mdx index eaa6331263..bb9afc2a4c 100644 --- a/apps/www/content/docs/index.mdx +++ b/apps/www/content/docs/index.mdx @@ -3,7 +3,7 @@ title: Introduction description: The rich-text editor framework for React. --- -Plate is a powerful toolkit that makes it easier for you to develop with Slate, a popular framework for building text editors. Plate focuses on four main areas: **Core**, **Plugins**, **Primitives** and **Components**. +Plate is a powerful toolkit that makes it easier for you to develop with [Slate](https://docs.slatejs.org/), a popular framework for building text editors. Plate focuses on four main areas: **Core**, **Plugins**, **Primitives** and **Components**. ## Core @@ -20,11 +20,11 @@ Plate offers a wide range of plugin packages that enhance the behavior, hooks, s ## Primitives -In addition to plugins, Plate provides unstyled and accessible components based on Radix UI. These components serve as the foundation for building high-quality design systems. +In addition to plugins, Plate provides unstyled and accessible components based on [Radix UI](https://www.radix-ui.com/). These components serve as the foundation for building high-quality design systems. ## Components -To help you get started with a visually appealing interface, Plate offers pre-built styled components you can own using our CLI. These components, based on shadcn/ui, can be used as a reference or starting point for your own component library, enabling you to create a unique, accessible, and visually pleasing user interface. +To help you get started with a visually appealing interface, Plate offers pre-built styled components you can own using our CLI. These components, based on [shadcn/ui](https://ui.shadcn.com/), can be used as a reference or starting point for your own component library, enabling you to create a unique, accessible, and visually pleasing user interface. ## FAQ @@ -36,7 +36,7 @@ Which frameworks are supported? -Slate and Slate React. +[Slate](https://www.npmjs.com/package/slate) and [Slate React](https://www.npmjs.com/package/slate-react). @@ -46,7 +46,7 @@ Which frameworks are supported? Can I use this in my project? -Yes. Free to use for personal and commercial projects. No attribution required, but sharing your editor app here is appreciated! +Yes. Free to use for personal and commercial projects. No attribution required, but sharing your editor app [here](https://github.com/udecode/plate/discussions/2523) is appreciated! @@ -56,8 +56,8 @@ Yes. Free to use for personal and commercial projects. No attribution required, ## Credits -- shadcn/ui - For the UI, the docs and the CLI. -- Radix UI - For the primitives. -- Vercel - For hosting. -- Shu Ding - The typography style is adapted from his work on Nextra. -- cmdk - For the `` component. +- [shadcn/ui](https://ui.shadcn.com/) - For the UI, the docs and the CLI. +- [Radix UI](https://radix-ui.com) - For the primitives. +- [Vercel](https://vercel.com) - For hosting. +- [Shu Ding](https://shud.in) - The typography style is adapted from his work on [Nextra](https://nextra.site). +- [cmdk](https://cmdk.paco.me) - For the `` component. diff --git a/apps/www/content/docs/media-placeholder.mdx b/apps/www/content/docs/media-placeholder.mdx new file mode 100644 index 0000000000..64af920cfc --- /dev/null +++ b/apps/www/content/docs/media-placeholder.mdx @@ -0,0 +1,129 @@ +--- +title: Media Placeholder +description: Media placeholders to be used as clickable placeholders for various media types (image, video, audio, file). +docs: + - route: https://pro.platejs.org/docs/components/media-placeholder-element + title: Placeholder Plugin +--- + + + +## Features + +- Supports multiple media types: image, video, audio, and file +- Transforms for inserting different types of media placeholders + + + +## Installation + +```bash +npm install @udecode/plate-media +``` + +## Usage + +```tsx +import { + AudioPlugin, + FilePlugin, + ImagePlugin, + MediaEmbedPlugin, + PlaceholderPlugin, + VideoPlugin, +} from '@udecode/plate-media/react'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + PlaceholderPlugin, +]; +``` + +```tsx +const components = { + // ...otherComponents, + [PlaceholderPlugin.key]: MediaPlaceholderElement, +}; +``` + +- [MediaPlaceholderElement](https://pro.platejs.org/docs/components/media-placeholder-element) (Plus) + +## Examples + +### Plate UI + +Work in progress. + +### Plate Plus + +- Displays clickable placeholders for various media types (image, video, audio, file) +- Opens a popover with two tabs when the placeholder is clicked: + - Upload tab: Allows uploading local files directly + - Embed tab: Enables pasting embed links for media content +- Automatically converts the placeholder to the appropriate media element (image, video, audio, file) once the upload or embed is submitted +- Validates URLs and file types for each media category +- Beautifully crafted UI + +{/* TODO: Add placeholder example */} + + + +## Plugins + +### PlaceholderPlugin + +Media placeholder element plugin. + +## Transforms + +### editor.tf.insert.audioPlaceholder + +Inserts an audio placeholder element. + + + + Options for the insert nodes transform. + + + +### editor.tf.insert.filePlaceholder + +Inserts a file placeholder element. + + + + Options for the insert nodes transform. + + + +### editor.tf.insert.imagePlaceholder + +Inserts an image placeholder element. + + + + Options for the insert nodes transform. + + + +### editor.tf.insert.videoPlaceholder + +Inserts a video placeholder element. + + + + Options for the insert nodes transform. + + + +## Types + +### TPlaceholderElement + +```tsx +type TPlaceholderElement = TElement & { + mediaType: string; +}; +``` diff --git a/apps/www/content/docs/media-toolbar.mdx b/apps/www/content/docs/media-toolbar.mdx new file mode 100644 index 0000000000..e73b0dfc01 --- /dev/null +++ b/apps/www/content/docs/media-toolbar.mdx @@ -0,0 +1,52 @@ +--- +title: Media Toolbar +description: allows you to insert images into your editor. +docs: + - route: https://pro.platejs.org/docs/components/media-toolbar + title: Media Toolbar +--- + + + + + +## Features + +- todo + + + +## Installation + +```bash +npm install @udecode/plate-ai +``` + +## Usage + +```tsx +// ... +import { AIPlugin } from '@/registry/default/plate-pro/ai/ai/src/react/AIPlugin'; + +const editor = usePlateEditor({ + id: 'ai-demo', + override: { + components: PlateUI, + }, + plugins: [ + ...commonPlugins, + SelectionOverlayPlugin, + MarkdownPlugin.configure({ options: { indentList: true } }), + AIPlugin.configure({ + options: { + scrollContainerSelector: '#scroll_container', + }, + render: { aboveEditable: AIMenu }, + }), + ], + value: aiValue, +}); +``` + +## API + diff --git a/apps/www/content/docs/media.mdx b/apps/www/content/docs/media.mdx index e4121eabe6..155e6e8820 100644 --- a/apps/www/content/docs/media.mdx +++ b/apps/www/content/docs/media.mdx @@ -10,6 +10,8 @@ docs: title: Media Popover - route: /docs/components/media-toolbar-button title: Media Toolbar Button + - route: https://pro.platejs.org/docs/components/media-toolbar + title: Media Toolbar --- @@ -36,10 +38,7 @@ npm install @udecode/plate-media ```tsx import { CaptionPlugin } from '@udecode/plate-caption/react'; -import { - ImagePlugin, - MediaEmbedPlugin, -} from '@udecode/plate-media/react'; +import { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react'; import { SelectOnBackspacePlugin } from '@udecode/plate-select'; const plugins = [ @@ -84,6 +83,48 @@ Disables URL embed on data insertion if set to true. Options extends `MediaPluginOptions`. +## API Placeholder + +### editor.tf.insert.audioPlaceholder + +Inserts a placeholder. Converts to an audio element when completed. + + + Options for the insert nodes transform. + + + + + +### editor.tf.insert.filePlaceholder + +Inserts a placeholder. Converts to a file element when completed. + + + Options for the insert nodes transform. + + + + +### editor.tf.insert.imagePlaceholder + +Inserts a placeholder. Converts to an image element when completed. + + + Options for the insert nodes transform. + + + +### editor.tf.insert.videoPlaceholder + +Inserts a placeholder. Converts to a video element when completed. + + + Options for the insert nodes transform. + + + + ## API Media ### insertMedia diff --git a/apps/www/content/docs/playwright.mdx b/apps/www/content/docs/playwright.mdx index f6adbd555f..756043b52a 100644 --- a/apps/www/content/docs/playwright.mdx +++ b/apps/www/content/docs/playwright.mdx @@ -146,7 +146,7 @@ The best workaround is to interact with the editor in the same way that a user w If this isn't practical, you can instead call a method on the `editor` object inside an `evaluate` or `evaluateHandle`. (Use `evaluateHandle` if you need to return a reference to a DOM node or a JavaScript object from the browser. Use `evaluate` if you need to return a serialized copy of a JavaScript object, or if you don't need to return any value.) -Note that while these queries and transforms can't be directly used in Playwright tests, they are available when working with the editor instance in your application code. For more information on how to use these methods in your application, refer to the Editor Methods documentation. +Note that while these queries and transforms can't be directly used in Playwright tests, they are available when working with the editor instance in your application code. For more information on how to use these methods in your application, refer to the [Editor Methods](/docs/editor-methods) documentation. See [Playwright's docs](https://playwright.dev/docs/evaluating) for more information about `evaluate` and `evaluateHandle`. @@ -292,4 +292,4 @@ The path to the node. The type of the node at the specified path. - \ No newline at end of file + diff --git a/apps/www/content/docs/plugin.mdx b/apps/www/content/docs/plugin.mdx index e285f7a1bc..3fdf98b555 100644 --- a/apps/www/content/docs/plugin.mdx +++ b/apps/www/content/docs/plugin.mdx @@ -168,7 +168,7 @@ A paragraph node affected by the above plugin would look like this: Occasionally, you'll need to override the built-in editor methods provided by Slate to work around bugs or add complex functionality. To do this, you can use the `extendEditor` plugin option to directly mutate properties of the `editor` object after its creation. -One common application of this technique is to create custom normalizers. +One common application of this technique is to create custom [normalizers](https://docs.slatejs.org/concepts/11-normalizing). ```ts showLineNumbers {20} const CustomNormalizerPlugin = createPlatePlugin({ diff --git a/apps/www/content/docs/slash-command.mdx b/apps/www/content/docs/slash-command.mdx new file mode 100644 index 0000000000..553fbee5d9 --- /dev/null +++ b/apps/www/content/docs/slash-command.mdx @@ -0,0 +1,122 @@ +--- +title: Slash Command +description: Slash command menu for quick insertion of various content types. +docs: + - route: components/slash-input-element + title: Slash Input Element + - route: https://pro.platejs.org/docs/components/slash-input-element + title: Slash Input Element +--- + + + + + +## Features + +- Quick access to various editor commands +- Keyboard navigation and filtering + {/* - AI assistance integration */} + + + +## Installation + +```bash +npm install @udecode/plate-slash-command +``` + +## Usage + +```tsx +import { SlashPlugin, SlashInputPlugin } from '@udecode/plate-slash-command/react'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + SlashPlugin, +]; +``` + +```tsx +const components = { + // ...otherComponents, + [SlashInputPlugin.key]: SlashInputElement, +}; +``` + +- [SlashInputElement](/components/slash-input-element) + +How to use: + +1. Type `/` anywhere in your document to open the slash menu. +2. Start typing to filter options or use arrow keys to navigate. +3. Press Enter or click to select an option. +4. Press Escape to close the menu without selecting. + +Available options include: + +- Headings +- Lists + + + 💡 Use keywords to quickly find options. For example, type '/ul' for Bulleted List. + + +## Examples + +### Plate UI + +Refer to the preview above. + +### Plate Plus + +- Extended set of slash menu options like "Ask AI" +- Trigger slash menu by click the + button on the left gutter. +- Item groups +- Beautifully crafted UI + + + +## Plugins + +### SlashPlugin + +Extends [TriggerComboboxPlugin](/docs/combobox#options) + + + + Function to create the combobox input element. + +- **Default:** + +```tsx +() => ({ + children: [{ text: '' }], + type: SlashInputPlugin.key, +}); +``` + + + + + The character that triggers the slash command combobox. + +- **Default:** `'/'` + + + + Regular expression to match the character before the trigger. + +- **Default:** `/^\s?$/` + + + +### SlashInputPlugin + +Plugin for slash input functionality. diff --git a/apps/www/content/docs/tabbable.mdx b/apps/www/content/docs/tabbable.mdx index a63e37595e..347a11d76c 100644 --- a/apps/www/content/docs/tabbable.mdx +++ b/apps/www/content/docs/tabbable.mdx @@ -58,7 +58,7 @@ query: (editor) => !!findNode(editor, { ### Non-void Slate nodes -One `TabbableEntry` will be created for each tabbable DOM element in the editor, as determined using the tabbable NPM package. The list of tabbables is then filtered using `isTabbable`. +One `TabbableEntry` will be created for each tabbable DOM element in the editor, as determined using the [tabbable](https://www.npmjs.com/package/tabbable) NPM package. The list of tabbables is then filtered using `isTabbable`. By default, `isTabbable` only returns true for entries inside void Slate nodes. You can override `isTabbable` to add support for DOM elements contained in other types of Slate node. diff --git a/apps/www/content/docs/toc.mdx b/apps/www/content/docs/toc.mdx new file mode 100644 index 0000000000..b3e065c3e8 --- /dev/null +++ b/apps/www/content/docs/toc.mdx @@ -0,0 +1,276 @@ +--- +title: Table of Contents +description: Renders a table of contents element with clickable links to headings in the document. +docs: + - route: components/toc-element + title: Toc Element + - route: https://pro.platejs.org/docs/components/toc-sidebar + title: Toc Sidebar +--- + + + + + +## Features + +- Automatically generates a table of contents from document headings +- Smooth scrolling to headings + + + +## Installation + +```bash +npm install @udecode/plate-heading @udecode/plate-node-id +``` + +## Usage + +```tsx +import { TocPlugin, HeadingPlugin } from '@udecode/plate-heading/react'; +import { NodeIdPlugin } from '@udecode/plate-node-id'; +``` + +```tsx +const plugins = [ + // ...otherPlugins, + HeadingPlugin, + NodeIdPlugin, + TocPlugin.configure({ + options: { + scrollContainerSelector: `#your-scroll-container-id`, + topOffset: 80, + }, + }), +]; +``` + +```tsx +const components = { + // ...otherComponents, + [TocPlugin.key]: TocElement, +}; +``` + +- [TocElement](/docs/components/toc-element) + +### Set scroll container + +The `TocPlugin` requires a scroll container selector to function properly. This selector is used to identify the scrollable area containing your content. + +```tsx +TocPlugin.configure({ + options: { + scrollContainerSelector: '#your-scroll-container-id', + }, +}) +``` + +You can use any valid CSS selector: + +- ID: `#scrollable-container` +- Class: `.content-wrapper` +- Nested selector: `body #main-content` + +Apply this selector to your `` component or a wrapper `
` around it, depending on your layout. + +For consistency, use the same container for both `TocPlugin` and `BlockSelectionPlugin`. + +### Components + +- [`TocElement`](/docs/components/toc-element) + +## Examples + +### Plate UI + +Refer to the preview above. + +### Plate Plus + +- Sticky TOC sidebar +- Hover-to-expand: Opens automatically when you move your mouse over it +- Interactive navigation: Click on items to smoothly scroll to the corresponding heading +- Visual feedback: Highlights the current section in the sidebar +- Beautifully crafted UI + + + + +## Plugins + +### TocPlugin + + + +Whether to use scrolling behavior. + +- **Default:** `true` + + + +The top offset to apply when scrolling to a heading. + +- **Default:** `80` + + + +A custom function to query headings from the editor. + + + +The CSS selector for the scrollable container. Required to use scrolling behavior. + +- **Default:** `'#scroll_container'` + + + +## Transforms + +### insertToc + +Inserts a table of contents element into the editor. + + + + The editor instance. + + + Options for inserting the TOC node. + + + +## Hooks + +### useTocElementState + + + + The editor instance. + + + An array of heading objects in the document. + + + A function to handle scrolling to a specific heading. + + + +### useTocElement + + + + The editor instance. + + + The scroll handler function from `useTocElementState`. + + + + + + + + A click handler for TOC items. + + + + + +### useTocSideBarState + +This hook manages the state for the TOC sidebar. + + + + Whether the TOC sidebar is open. + - **Default:** `true` + + + Root margin for the Intersection Observer. + - **Default:** `'0px 0px 0px 0px'` + + + Top offset for scrolling. + - **Default:** `0` + + + + + + ID of the currently active content section. + + + The editor instance. + + + List of headings in the document. + + + Whether the mouse is currently over the TOC. + + + Whether the TOC sidebar is open. + + + Function to set the observation state. + + + Function to set whether the mouse is over the TOC. + + + Ref for the TOC element. + + + Function to handle content scrolling. + + + +### useTocSideBar + +This hook provides props and handlers for the TOC sidebar component. + + + + The editor instance. + + + Whether the mouse is currently over the TOC. + + + Whether the TOC sidebar is open. + + + Function to set the observation state. + + + Function to set whether the mouse is over the TOC. + + + Ref for the TOC element. + + + Function to handle content scrolling. + + + + + + Props for the TOC navigation element. + + + Ref for the TOC element. + + + Handler for mouse enter event. + + + Handler for mouse leave event. + + + + + Handler for clicking on a TOC item. + + diff --git a/apps/www/content/docs/upload.mdx b/apps/www/content/docs/upload.mdx new file mode 100644 index 0000000000..af6ff194cd --- /dev/null +++ b/apps/www/content/docs/upload.mdx @@ -0,0 +1,63 @@ +--- +title: Upload +description: Allows you to upload files and images into your editor. +docs: + - route: https://pro.platejs.org/docs/components/upload + title: Upload +--- + + + + + +## Features + +- Integration with [UploadThing](https://uploadthing.com/) +- Support for various media types: images, videos, audio, and files +{/* - Multiple upload methods: + - Drag & drop files from your computer + - Paste images directly from clipboard */} +- Use slash commands for quick insertion +- Image-specific features: + - Resize using vertical edge bars + - Alignment options + - Caption support + - Expand/collapse view + - Easy download +- Video-specific features: + - Resize using vertical edge bars + - Alignment options + - Caption support + - View original source +- Audio and file upload support +{/* - Block menu for easy modification of uploaded content */} +- Ability to embed media via URL + + + +{/* ### UploadThing Integration + +This component uses UploadThing for file uploads. UploadThing provides a simple and efficient way to handle file uploads in your application. + +To use UploadThing: + +1. Set up an UploadThing account and configure your upload endpoints. +2. Install the UploadThing client in your project: + +```bash +npm install uploadthing +``` + +3. Configure the UploadThing client in your application. + +For more details on setting up UploadThing, refer to their [documentation](https://docs.uploadthing.com/). */} + +## Examples + +### Plate UI + +Work in progress. + +### Plate Plus + +Refer to the preview above. \ No newline at end of file diff --git a/apps/www/contentlayer.config.js b/apps/www/contentlayer.config.js index 94dc5fae42..a7b0a2333c 100644 --- a/apps/www/contentlayer.config.js +++ b/apps/www/contentlayer.config.js @@ -181,7 +181,8 @@ export default makeSource({ { properties: { ariaLabel: 'Link to section', - className: ['subheading-anchor'], + className: ['subheading-anchor group/subheading'], + 'data-empty': 'true', }, }, ], diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts index fd36f9494e..725dd6f245 100644 --- a/apps/www/next-env.d.ts +++ b/apps/www/next-env.d.ts @@ -3,4 +3,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/www/next.config.mjs b/apps/www/next.config.ts similarity index 79% rename from apps/www/next.config.mjs rename to apps/www/next.config.ts index 79f32053e8..69dcb75eb0 100644 --- a/apps/www/next.config.mjs +++ b/apps/www/next.config.ts @@ -1,18 +1,9 @@ -import { globSync } from 'glob'; +import type { NextConfig } from 'next'; -const nextConfig = async (phase, { defaultConfig }) => { - /** @type {import('next').NextConfig} */ - const config = { - // Enable React strict mode. - // Enable experimental features. - experimental: { - esmExternals: false, - // Specify external packages that should be excluded from server-side rendering. - // https://beta.nextjs.org/docs/api-reference/next-config#servercomponentsexternalpackages - serverComponentsExternalPackages: ['@prisma/client'], - }, +import { globSync } from 'glob'; - // Configure domains to allow for optimized image loading. +const nextConfig = async (phase: string) => { + const config: NextConfig = { // https://nextjs.org/docs/basic-features/image-optimization#domains images: { remotePatterns: [ @@ -35,6 +26,10 @@ const nextConfig = async (phase, { defaultConfig }) => { ], }, + // Configure domains to allow for optimized image loading. + // https://nextjs.org/docs/api-reference/next.config.js/react-strict-mod + reactStrictMode: true, + // typescript: { // ignoreBuildErrors: true, // }, @@ -42,8 +37,7 @@ const nextConfig = async (phase, { defaultConfig }) => { // ignoreDuringBuilds: true, // }, - // https://nextjs.org/docs/api-reference/next.config.js/react-strict-mod - reactStrictMode: true, + serverExternalPackages: ['@prisma/client'], staticPageGenerationTimeout: 1200, @@ -86,8 +80,8 @@ const nextConfig = async (phase, { defaultConfig }) => { if (phase === 'phase-development-server') { const fs = await import('node:fs'); - const packageNames = new globSync('../../packages/**/package.json') - .map((file) => { + const packageNames = globSync('../../packages/**/package.json') + .map((file: any) => { try { const packageJson = JSON.parse(fs.readFileSync(file, 'utf8')); diff --git a/apps/www/package.json b/apps/www/package.json index 2d47c49b38..fcd52c7871 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -9,8 +9,7 @@ "build": "yarn prebuild && yarn build:registry && next build", "build:contentlayer": "contentlayer2 build", "build:registry": "tsx --tsconfig ./scripts/tsconfig.scripts.json scripts/build-registry.mts", - "dev": "concurrently \"contentlayer2 dev\" \"NODE_OPTIONS=--max-old-space-size=16192 next dev\"", - "dev:quick": "NODE_OPTIONS=--max-old-space-size=16192 next dev", + "dev": "concurrently \"contentlayer2 dev\" \"next dev --turbo\"", "lint": "npx eslint . --ext .js,.jsx,.ts,.tsx", "lint:debug": "TIMING=1 DEBUG=eslint:cli-engine yarn lint:fix", "lint:fix": "yarn lint --fix", @@ -35,8 +34,10 @@ "react-dom": "^18.3.1" }, "dependencies": { + "@ai-sdk/openai": "^0.0.67", "@ariakit/react": "0.4.11", - "@next/third-parties": "14.2.13", + "@faker-js/faker": "^9.0.2", + "@next/third-parties": "15.0.0", "@radix-ui/colors": "3.0.0", "@radix-ui/react-accessible-icon": "^1.1.0", "@radix-ui/react-accordion": "^1.2.0", @@ -70,6 +71,7 @@ "@radix-ui/react-tooltip": "^1.1.2", "@udecode/cn": "workspace:^", "@udecode/plate": "workspace:^", + "@udecode/plate-ai": "workspace:^", "@udecode/plate-alignment": "workspace:^", "@udecode/plate-autoformat": "workspace:^", "@udecode/plate-basic-elements": "workspace:^", @@ -131,6 +133,7 @@ "@udecode/slate-utils": "workspace:^", "@udecode/utils": "workspace:^", "@vercel/og": "^0.6.2", + "ai": "^3.4.10", "class-variance-authority": "^0.7.0", "cmdk": "^1.0.0", "contentlayer2": "^0.4.6", @@ -138,10 +141,11 @@ "framer-motion": "^11.5.4", "lodash.template": "^4.5.0", "lucide-react": "^0.441.0", - "next": "14.3.0-canary.43", + "match-sorter": "6.3.4", + "next": "15.0.0", "next-contentlayer2": "^0.4.6", "next-themes": "^0.3.0", - "nuqs": "^1.19.1", + "nuqs": "^2.0.3", "prismjs": "^1.29.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", diff --git a/apps/www/public/ai-selection.png b/apps/www/public/ai-selection.png new file mode 100644 index 0000000000..a512f46326 Binary files /dev/null and b/apps/www/public/ai-selection.png differ diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 5b221cbc50..95d27c3c84 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -11,6 +11,49 @@ "registryDependencies": [], "type": "registry:ui" }, + { + "dependencies": [ + "@udecode/plate-ai", + "@udecode/plate-markdown", + "@udecode/plate-selection" + ], + "files": [ + { + "path": "plate-ui/ai-menu.tsx", + "type": "registry:ui" + }, + { + "path": "plate-ui/ai-chat-editor.tsx", + "type": "registry:ui" + }, + { + "path": "plate-ui/ai-menu-items.tsx", + "type": "registry:ui" + } + ], + "name": "ai-menu", + "registryDependencies": [ + "button", + "menu", + "textarea", + "editor" + ], + "type": "registry:ui" + }, + { + "dependencies": [], + "files": [ + { + "path": "plate-ui/ai-toolbar-button.tsx", + "type": "registry:ui" + } + ], + "name": "ai-toolbar-button", + "registryDependencies": [ + "toolbar" + ], + "type": "registry:ui" + }, { "dependencies": [ "@udecode/plate-selection" @@ -323,6 +366,71 @@ ], "type": "registry:ui" }, + { + "dependencies": [ + "@radix-ui/react-context-menu" + ], + "files": [ + { + "path": "plate-ui/block-context-menu.tsx", + "type": "registry:ui" + } + ], + "name": "block-context-menu", + "registryDependencies": [ + "calendar", + "plate-element" + ], + "type": "registry:ui" + }, + { + "dependencies": [ + "@udecode/plate-ai" + ], + "files": [ + { + "path": "plate-ui/ghost-text.tsx", + "type": "registry:ui" + } + ], + "name": "ghost-text", + "registryDependencies": [ + "" + ], + "type": "registry:ui" + }, + { + "dependencies": [ + "@udecode/plate-ai" + ], + "files": [ + { + "path": "plate-ui/ai-leaf.tsx", + "type": "registry:ui" + } + ], + "name": "ai-leaf", + "registryDependencies": [ + "" + ], + "type": "registry:ui" + }, + { + "dependencies": [ + "@udecode/plate-heading" + ], + "files": [ + { + "path": "plate-ui/toc-element.tsx", + "type": "registry:ui" + } + ], + "name": "toc-element", + "registryDependencies": [ + "" + ], + "type": "registry:ui" + }, { "dependencies": [ "react-day-picker@8.10.1", @@ -482,7 +590,8 @@ }, { "dependencies": [ - "@radix-ui/react-dialog" + "@radix-ui/react-dialog", + "@radix-ui/react-visually-hidden" ], "files": [ { @@ -711,11 +820,11 @@ ], "files": [ { - "path": "plate-ui/indent-todo-marker-component.tsx", + "path": "plate-ui/indent-todo-marker.tsx", "type": "registry:ui" } ], - "name": "indent-todo-marker-component", + "name": "indent-todo-marker", "registryDependencies": [ "checkbox" ], diff --git a/apps/www/public/r/styles/default/ai-leaf.json b/apps/www/public/r/styles/default/ai-leaf.json new file mode 100644 index 0000000000..6f01629c8e --- /dev/null +++ b/apps/www/public/r/styles/default/ai-leaf.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "@udecode/plate-ai" + ], + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\n\nimport { cn, withRef } from '@udecode/cn';\nimport { PlateLeaf } from '@udecode/plate-common/react';\n\nexport const AILeaf = withRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n", + "path": "plate-ui/ai-leaf.tsx", + "target": "components/plate-ui/ai-leaf.tsx", + "type": "registry:ui" + } + ], + "name": "ai-leaf", + "registryDependencies": [ + "" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/ai-menu.json b/apps/www/public/r/styles/default/ai-menu.json new file mode 100644 index 0000000000..50a226e156 --- /dev/null +++ b/apps/www/public/r/styles/default/ai-menu.json @@ -0,0 +1,35 @@ +{ + "dependencies": [ + "@udecode/plate-ai", + "@udecode/plate-markdown", + "@udecode/plate-selection" + ], + "files": [ + { + "content": "import * as React from 'react';\n\nimport { faker } from '@faker-js/faker';\nimport { AIChatPlugin, useEditorChat } from '@udecode/plate-ai/react';\nimport {\n type TElement,\n type TNodeEntry,\n getAncestorNode,\n getBlocks,\n isElementEmpty,\n isHotkey,\n isSelectionAtBlockEnd,\n} from '@udecode/plate-common';\nimport {\n type PlateEditor,\n toDOMNode,\n useEditorPlugin,\n useHotkeys,\n} from '@udecode/plate-common/react';\nimport {\n BlockSelectionPlugin,\n useIsSelecting,\n} from '@udecode/plate-selection/react';\nimport { useChat } from 'ai/react';\nimport { Loader2Icon } from 'lucide-react';\n\nimport { AIChatEditor } from './ai-chat-editor';\nimport { AIMenuItems } from './ai-menu-items';\nimport { Command, CommandList, InputCommand } from './command';\nimport { Popover, PopoverAnchor, PopoverContent } from './popover';\n\nexport function AIMenu() {\n const { api, editor, useOption } = useEditorPlugin(AIChatPlugin);\n const open = useOption('open');\n const mode = useOption('mode');\n const isSelecting = useIsSelecting();\n\n const aiEditorRef = React.useRef(null);\n const [value, setValue] = React.useState('');\n\n const chat = useChat({\n id: 'editor',\n // API to be implemented\n api: '/api/ai',\n // Mock the API response. Remove it when you implement the route /api/ai\n fetch: async () => {\n await new Promise((resolve) => setTimeout(resolve, 400));\n\n const stream = fakeStreamText();\n\n return new Response(stream, {\n headers: {\n Connection: 'keep-alive',\n 'Content-Type': 'text/plain',\n },\n });\n },\n });\n\n const { input, isLoading, messages, setInput } = chat;\n const [anchorElement, setAnchorElement] = React.useState(\n null\n );\n\n const setOpen = (open: boolean) => {\n if (open) {\n api.aiChat.show();\n } else {\n api.aiChat.hide();\n }\n };\n\n const show = (anchorElement: HTMLElement) => {\n setAnchorElement(anchorElement);\n setOpen(true);\n };\n\n useEditorChat({\n chat,\n onOpenBlockSelection: (blocks: TNodeEntry[]) => {\n show(toDOMNode(editor, blocks.at(-1)![0])!);\n },\n onOpenChange: (open) => {\n if (!open) {\n setAnchorElement(null);\n setInput('');\n }\n },\n onOpenCursor: () => {\n const ancestor = getAncestorNode(editor)?.[0] as TElement;\n\n if (!isSelectionAtBlockEnd(editor) && !isElementEmpty(editor, ancestor)) {\n editor\n .getApi(BlockSelectionPlugin)\n .blockSelection.addSelectedRow(ancestor.id as string);\n }\n\n show(toDOMNode(editor, ancestor)!);\n },\n onOpenSelection: () => {\n show(toDOMNode(editor, getBlocks(editor).at(-1)![0])!);\n },\n });\n\n useHotkeys(\n 'meta+j',\n () => {\n api.aiChat.show();\n },\n { enableOnContentEditable: true, enableOnFormTags: true }\n );\n\n return (\n \n \n\n {\n e.preventDefault();\n\n if (isLoading) {\n api.aiChat.stop();\n } else {\n api.aiChat.hide();\n }\n }}\n align=\"center\"\n avoidCollisions={false}\n side=\"bottom\"\n >\n \n {mode === 'chat' && isSelecting && messages.length > 0 && (\n \n )}\n\n {isLoading ? (\n
\n \n {messages.length > 1 ? 'Editing...' : 'Thinking...'}\n
\n ) : (\n {\n if (isHotkey('backspace')(e) && input.length === 0) {\n e.preventDefault();\n api.aiChat.hide();\n }\n if (isHotkey('enter')(e) && !e.shiftKey && !value) {\n e.preventDefault();\n void api.aiChat.submit();\n }\n }}\n onValueChange={setInput}\n placeholder=\"Ask AI anything...\"\n autoFocus\n />\n )}\n\n {!isLoading && (\n \n \n \n )}\n \n \n
\n );\n}\n\n// Used for testing. Remove it after implementing useChat api.\nconst fakeStreamText = ({\n chunkCount = 10,\n streamProtocol = 'data',\n}: {\n chunkCount?: number;\n streamProtocol?: 'data' | 'text';\n} = {}) => {\n const chunks = Array.from({ length: chunkCount }, () => ({\n delay: faker.number.int({ max: 150, min: 50 }),\n texts: faker.lorem.words({ max: 3, min: 1 }) + ' ',\n }));\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n for (const chunk of chunks) {\n await new Promise((resolve) => setTimeout(resolve, chunk.delay));\n\n if (streamProtocol === 'text') {\n controller.enqueue(encoder.encode(chunk.texts));\n } else {\n controller.enqueue(\n encoder.encode(`0:${JSON.stringify(chunk.texts)}\\n`)\n );\n }\n }\n\n if (streamProtocol === 'data') {\n controller.enqueue(\n `d:{\"finishReason\":\"stop\",\"usage\":{\"promptTokens\":0,\"completionTokens\":${chunks.length}}}\\n`\n );\n }\n\n controller.close();\n },\n });\n};\n", + "path": "plate-ui/ai-menu.tsx", + "target": "components/plate-ui/ai-menu.tsx", + "type": "registry:ui" + }, + { + "content": "'use client';\n\nimport React, { memo } from 'react';\n\nimport { AIChatPlugin, useLastAssistantMessage } from '@udecode/plate-ai/react';\nimport {\n type PlateEditor,\n Plate,\n useEditorPlugin,\n} from '@udecode/plate-common/react';\nimport { deserializeMd } from '@udecode/plate-markdown';\n\nimport { Editor } from './editor';\n\nexport const AIChatEditor = memo(\n ({\n aiEditorRef,\n }: {\n aiEditorRef: React.MutableRefObject;\n }) => {\n const { getOptions } = useEditorPlugin(AIChatPlugin);\n const lastAssistantMessage = useLastAssistantMessage();\n const content = lastAssistantMessage?.content ?? '';\n\n const aiEditor = React.useMemo(() => {\n const editor = getOptions().createAIEditor();\n\n const fragment = deserializeMd(editor, content);\n editor.children =\n fragment.length > 0 ? fragment : editor.api.create.value();\n\n return editor;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n React.useEffect(() => {\n if (aiEditor && content) {\n aiEditorRef.current = aiEditor;\n\n setTimeout(() => {\n aiEditor.tf.setValue(deserializeMd(aiEditor, content));\n }, 0);\n }\n }, [aiEditor, aiEditorRef, content]);\n\n if (!content) return null;\n\n return (\n \n \n \n );\n }\n);\n", + "path": "plate-ui/ai-chat-editor.tsx", + "target": "components/plate-ui/ai-chat-editor.tsx", + "type": "registry:ui" + }, + { + "content": "import { useEffect, useMemo } from 'react';\n\nimport { AIChatPlugin, AIPlugin } from '@udecode/plate-ai/react';\nimport {\n getAncestorNode,\n getEndPoint,\n getNodeString,\n} from '@udecode/plate-common';\nimport {\n type PlateEditor,\n focusEditor,\n useEditorPlugin,\n} from '@udecode/plate-common/react';\nimport { useIsSelecting } from '@udecode/plate-selection/react';\nimport {\n Album,\n BadgeHelp,\n Check,\n CornerUpLeft,\n FeatherIcon,\n ListEnd,\n ListMinus,\n ListPlus,\n PenLine,\n Wand,\n X,\n} from 'lucide-react';\n\nimport { CommandGroup, CommandItem } from './command';\n\nexport type EditorChatState =\n | 'cursorCommand'\n | 'cursorSuggestion'\n | 'selectionCommand'\n | 'selectionSuggestion';\n\nexport const aiChatItems = {\n accept: {\n icon: ,\n label: 'Accept',\n value: 'accept',\n onSelect: ({ editor }) => {\n editor.getTransforms(AIChatPlugin).aiChat.accept();\n focusEditor(editor, getEndPoint(editor, editor.selection!));\n },\n },\n continueWrite: {\n icon: ,\n label: 'Continue writing',\n value: 'continueWrite',\n onSelect: ({ editor }) => {\n const ancestorNode = getAncestorNode(editor);\n const isEmpty = getNodeString(ancestorNode![0]).trim().length === 0;\n\n void editor.getApi(AIChatPlugin).aiChat.submit({\n mode: 'insert',\n prompt: isEmpty\n ? `\n{editor}\n\nStart writing a new paragraph AFTER ONLY ONE SENTENCE`\n : 'Continue writing AFTER ONLY ONE SENTENCE. DONT REPEAT THE TEXT.',\n });\n },\n },\n discard: {\n icon: ,\n label: 'Discard',\n shortcut: 'Escape',\n value: 'discard',\n onSelect: ({ editor }) => {\n editor.getTransforms(AIPlugin).ai.undo();\n editor.getApi(AIChatPlugin).aiChat.hide();\n },\n },\n explain: {\n icon: ,\n label: 'Explain',\n value: 'explain',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: {\n default: 'Explain {editor}',\n selecting: 'Explain',\n },\n });\n },\n },\n fixSpelling: {\n icon: ,\n label: 'Fix spelling & grammar',\n value: 'fixSpelling',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: 'Fix spelling and grammar',\n });\n },\n },\n improveWriting: {\n icon: ,\n label: 'Improve writing',\n value: 'improveWriting',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: 'Improve the writing',\n });\n },\n },\n insertBelow: {\n icon: ,\n label: 'Insert below',\n value: 'insertBelow',\n onSelect: ({ aiEditor, editor }) => {\n void editor.getTransforms(AIChatPlugin).aiChat.insertBelow(aiEditor);\n },\n },\n makeLonger: {\n icon: ,\n label: 'Make longer',\n value: 'makeLonger',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: 'Make longer',\n });\n },\n },\n makeShorter: {\n icon: ,\n label: 'Make shorter',\n value: 'makeShorter',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: 'Make shorter',\n });\n },\n },\n replace: {\n icon: ,\n label: 'Replace selection',\n value: 'replace',\n onSelect: ({ aiEditor, editor }) => {\n void editor.getTransforms(AIChatPlugin).aiChat.replaceSelection(aiEditor);\n },\n },\n simplifyLanguage: {\n icon: ,\n label: 'Simplify language',\n value: 'simplifyLanguage',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n prompt: 'Simplify the language',\n });\n },\n },\n summarize: {\n icon: ,\n label: 'Add a summary',\n value: 'summarize',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.submit({\n mode: 'insert',\n prompt: {\n default: 'Summarize {editor}',\n selecting: 'Summarize',\n },\n });\n },\n },\n tryAgain: {\n icon: ,\n label: 'Try again',\n value: 'tryAgain',\n onSelect: ({ editor }) => {\n void editor.getApi(AIChatPlugin).aiChat.reload();\n },\n },\n} satisfies Record<\n string,\n {\n icon: React.ReactNode;\n label: string;\n value: string;\n component?: React.ComponentType<{ menuState: EditorChatState }>;\n filterItems?: boolean;\n items?: { label: string; value: string }[];\n shortcut?: string;\n onSelect?: ({\n aiEditor,\n editor,\n }: {\n aiEditor: PlateEditor;\n editor: PlateEditor;\n }) => void;\n }\n>;\n\nconst menuStateItems: Record<\n EditorChatState,\n {\n items: (typeof aiChatItems)[keyof typeof aiChatItems][];\n heading?: string;\n }[]\n> = {\n cursorCommand: [\n {\n items: [\n aiChatItems.continueWrite,\n aiChatItems.summarize,\n aiChatItems.explain,\n ],\n },\n ],\n cursorSuggestion: [\n {\n items: [aiChatItems.accept, aiChatItems.discard, aiChatItems.tryAgain],\n },\n ],\n selectionCommand: [\n {\n items: [\n aiChatItems.improveWriting,\n aiChatItems.makeLonger,\n aiChatItems.makeShorter,\n aiChatItems.fixSpelling,\n aiChatItems.simplifyLanguage,\n ],\n },\n ],\n selectionSuggestion: [\n {\n items: [\n aiChatItems.replace,\n aiChatItems.insertBelow,\n aiChatItems.discard,\n aiChatItems.tryAgain,\n ],\n },\n ],\n};\n\nexport const AIMenuItems = ({\n aiEditorRef,\n setValue,\n}: {\n aiEditorRef: React.MutableRefObject;\n setValue: (value: string) => void;\n}) => {\n const { editor, useOption } = useEditorPlugin(AIChatPlugin);\n const { messages } = useOption('chat');\n const isSelecting = useIsSelecting();\n\n const menuState = useMemo(() => {\n if (messages && messages.length > 0) {\n return isSelecting ? 'selectionSuggestion' : 'cursorSuggestion';\n }\n\n return isSelecting ? 'selectionCommand' : 'cursorCommand';\n }, [isSelecting, messages]);\n\n const menuGroups = useMemo(() => {\n const items = menuStateItems[menuState];\n\n return items;\n }, [menuState]);\n\n useEffect(() => {\n if (menuGroups.length > 0 && menuGroups[0].items.length > 0) {\n setValue(menuGroups[0].items[0].value);\n }\n }, [menuGroups, setValue]);\n\n return (\n <>\n {menuGroups.map((group, index) => (\n \n {group.items.map((menuItem) => (\n {\n menuItem.onSelect?.({\n aiEditor: aiEditorRef.current!,\n editor: editor,\n });\n }}\n >\n {menuItem.icon}\n {menuItem.label}\n \n ))}\n \n ))}\n \n );\n};\n", + "path": "plate-ui/ai-menu-items.tsx", + "target": "components/plate-ui/ai-menu-items.tsx", + "type": "registry:ui" + } + ], + "name": "ai-menu", + "registryDependencies": [ + "button", + "menu", + "textarea", + "editor" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/ai-toolbar-button.json b/apps/www/public/r/styles/default/ai-toolbar-button.json new file mode 100644 index 0000000000..94c9e7a3f2 --- /dev/null +++ b/apps/www/public/r/styles/default/ai-toolbar-button.json @@ -0,0 +1,16 @@ +{ + "dependencies": [], + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\n\nimport { withRef } from '@udecode/cn';\nimport { AIChatPlugin } from '@udecode/plate-ai/react';\nimport { useEditorPlugin } from '@udecode/plate-common/react';\n\nimport { ToolbarButton } from './toolbar';\n\nexport const AIToolbarButton = withRef(\n ({ children, ...rest }, ref) => {\n const { api } = useEditorPlugin(AIChatPlugin);\n\n return (\n {\n api.aiChat.show();\n }}\n >\n {children}\n \n );\n }\n);\n", + "path": "plate-ui/ai-toolbar-button.tsx", + "target": "components/plate-ui/ai-toolbar-button.tsx", + "type": "registry:ui" + } + ], + "name": "ai-toolbar-button", + "registryDependencies": [ + "toolbar" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/block-context-menu.json b/apps/www/public/r/styles/default/block-context-menu.json new file mode 100644 index 0000000000..f928f45bc6 --- /dev/null +++ b/apps/www/public/r/styles/default/block-context-menu.json @@ -0,0 +1,19 @@ +{ + "dependencies": [ + "@radix-ui/react-context-menu" + ], + "files": [ + { + "content": "import { useCallback, useState } from 'react';\n\nimport { AIChatPlugin } from '@udecode/plate-ai/react';\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { ParagraphPlugin, useEditorPlugin } from '@udecode/plate-core/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { IndentListPlugin } from '@udecode/plate-indent-list/react';\nimport {\n BLOCK_CONTEXT_MENU_ID,\n BlockMenuPlugin,\n BlockSelectionPlugin,\n} from '@udecode/plate-selection/react';\nimport { unsetNodes } from '@udecode/slate';\nimport { focusEditor } from '@udecode/slate-react';\n\nimport {\n ContextMenu,\n ContextMenuContent,\n ContextMenuItem,\n ContextMenuSeparator,\n ContextMenuShortcut,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,\n ContextMenuTrigger,\n} from './context-menu';\n\ntype Value = 'askAI' | null;\n\nexport function BlockContextMenu({ children }: { children: React.ReactNode }) {\n const { api, editor } = useEditorPlugin(BlockMenuPlugin);\n const [value, setValue] = useState(null);\n\n const handleTurnInto = useCallback(\n (type: string) => {\n editor\n .getApi(BlockSelectionPlugin)\n .blockSelection.getNodes()\n .forEach(([node, path]) => {\n if (node[IndentListPlugin.key]) {\n unsetNodes(editor, [IndentListPlugin.key, 'indent'], { at: path });\n }\n\n editor.tf.toggle.block({ type }, { at: path });\n });\n },\n [editor]\n );\n\n const handleAlign = useCallback(\n (align: 'center' | 'left' | 'right') => {\n editor\n .getTransforms(BlockSelectionPlugin)\n .blockSelection.setNodes({ align });\n },\n [editor]\n );\n\n return (\n \n {\n const dataset = (event.target as HTMLElement).dataset;\n\n const disabled = dataset?.slateEditor === 'true';\n\n if (disabled) return event.preventDefault();\n\n api.blockMenu.show(BLOCK_CONTEXT_MENU_ID, {\n x: event.clientX,\n y: event.clientY,\n });\n }}\n >\n {children}\n \n {\n e.preventDefault();\n\n if (value === 'askAI') {\n editor.getApi(AIChatPlugin).aiChat.show();\n }\n\n setValue(null);\n }}\n >\n {\n setValue('askAI');\n }}\n >\n Ask AI\n \n {\n editor\n .getTransforms(BlockSelectionPlugin)\n .blockSelection.removeNodes();\n focusEditor(editor);\n }}\n >\n Delete\n \n {\n editor\n .getTransforms(BlockSelectionPlugin)\n .blockSelection.duplicate(\n editor.getApi(BlockSelectionPlugin).blockSelection.getNodes()\n );\n }}\n >\n Duplicate\n ⌘ + D\n \n \n Turn into\n \n handleTurnInto(ParagraphPlugin.key)}\n >\n Paragraph\n \n\n handleTurnInto(HEADING_KEYS.h1)}>\n Heading 1\n \n handleTurnInto(HEADING_KEYS.h2)}>\n Heading 2\n \n handleTurnInto(HEADING_KEYS.h3)}>\n Heading 3\n \n handleTurnInto(BlockquotePlugin.key)}\n >\n Blockquote\n \n \n \n \n \n editor\n .getTransforms(BlockSelectionPlugin)\n .blockSelection.setIndent(1)\n }\n >\n Indent\n \n \n editor\n .getTransforms(BlockSelectionPlugin)\n .blockSelection.setIndent(-1)\n }\n >\n Outdent\n \n \n Align\n \n handleAlign('left')}>\n Left\n \n handleAlign('center')}>\n Center\n \n handleAlign('right')}>\n Right\n \n \n \n \n \n );\n}\n", + "path": "plate-ui/block-context-menu.tsx", + "target": "components/plate-ui/block-context-menu.tsx", + "type": "registry:ui" + } + ], + "name": "block-context-menu", + "registryDependencies": [ + "calendar", + "plate-element" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/button.json b/apps/www/public/r/styles/default/button.json index dde5b94c11..a1caf00116 100644 --- a/apps/www/public/r/styles/default/button.json +++ b/apps/www/public/r/styles/default/button.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "import * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn, withRef } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n defaultVariants: {\n size: 'default',\n variant: 'default',\n },\n variants: {\n isMenu: {\n true: 'h-auto w-full cursor-pointer justify-start',\n },\n size: {\n default: 'h-10 px-4 py-2',\n icon: 'size-10',\n lg: 'h-11 rounded-md px-8',\n none: '',\n sm: 'h-9 rounded-md px-3',\n sms: 'size-9 rounded-md px-0',\n xs: 'h-8 rounded-md px-3',\n },\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n inlineLink: 'text-base text-primary underline underline-offset-4',\n link: 'text-primary underline-offset-4 hover:underline',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n },\n },\n }\n);\n\nexport const Button = withRef<\n 'button',\n {\n asChild?: boolean;\n } & VariantProps\n>(({ asChild = false, className, isMenu, size, variant, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n \n );\n});\n", + "content": "import * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn, withRef } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n defaultVariants: {\n size: 'default',\n variant: 'default',\n },\n variants: {\n isMenu: {\n true: 'h-auto w-full cursor-pointer justify-start',\n },\n size: {\n default: 'h-10 px-4 py-2',\n icon: 'size-10',\n lg: 'h-11 rounded-md px-8',\n none: '',\n sm: 'h-9 rounded-md px-3',\n sms: 'size-9 rounded-md px-0',\n xs: 'h-8 rounded-md px-3 text-xs',\n },\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n inlineLink: 'text-base text-primary underline underline-offset-4',\n link: 'text-primary underline-offset-4 hover:underline',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n },\n },\n }\n);\n\nexport const Button = withRef<\n 'button',\n {\n asChild?: boolean;\n } & VariantProps\n>(({ asChild = false, className, isMenu, size, variant, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n \n );\n});\n", "path": "plate-ui/button.tsx", "target": "components/plate-ui/button.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/command.json b/apps/www/public/r/styles/default/command.json index ffb7e4b93e..b174062222 100644 --- a/apps/www/public/r/styles/default/command.json +++ b/apps/www/public/r/styles/default/command.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DialogProps } from '@radix-ui/react-dialog';\n\nimport { cn, createPrimitiveElement, withCn, withRef } from '@udecode/cn';\nimport { Command as CommandPrimitive } from 'cmdk';\n\nimport { Icons } from '@/components/icons';\n\nimport { Dialog, DialogContent } from './dialog';\n\nexport const Command = withCn(\n CommandPrimitive,\n 'flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground'\n);\n\nexport function CommandDialog({ children, ...props }: DialogProps) {\n return (\n \n \n \n {children}\n \n \n \n );\n}\n\nexport const CommandInput = withRef(\n ({ className, ...props }, ref) => (\n
\n \n \n
\n )\n);\n\nexport const CommandList = withCn(\n CommandPrimitive.List,\n 'max-h-[500px] overflow-y-auto overflow-x-hidden'\n);\n\nexport const CommandEmpty = withCn(\n CommandPrimitive.Empty,\n 'py-6 text-center text-sm'\n);\n\nexport const CommandGroup = withCn(\n CommandPrimitive.Group,\n 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground'\n);\n\nexport const CommandSeparator = withCn(\n CommandPrimitive.Separator,\n '-mx-1 h-px bg-border'\n);\n\nexport const CommandItem = withCn(\n CommandPrimitive.Item,\n 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50'\n);\n\nexport const CommandShortcut = withCn(\n createPrimitiveElement('span'),\n 'ml-auto text-xs tracking-widest text-muted-foreground'\n);\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DialogProps } from '@radix-ui/react-dialog';\n\nimport {\n cn,\n createPrimitiveElement,\n withCn,\n withRef,\n withVariants,\n} from '@udecode/cn';\nimport { Command as CommandPrimitive } from 'cmdk';\n\nimport { Icons } from '@/components/icons';\n\nimport { Dialog, DialogContent, DialogTitle } from './dialog';\nimport { inputVariants } from './input';\n\nexport const Command = withCn(\n CommandPrimitive,\n 'flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground'\n);\n\nexport function CommandDialog({ children, ...props }: DialogProps) {\n return (\n \n \n Command Dialog\n \n {children}\n \n \n \n );\n}\n\nexport const CommandInput = withRef(\n ({ className, ...props }, ref) => (\n
\n \n \n
\n )\n);\n\nexport const InputCommand = withVariants(\n CommandPrimitive.Input,\n inputVariants,\n ['variant']\n);\n\nexport const CommandList = withCn(\n CommandPrimitive.List,\n 'max-h-[500px] overflow-y-auto overflow-x-hidden'\n);\n\nexport const CommandEmpty = withCn(\n CommandPrimitive.Empty,\n 'py-6 text-center text-sm'\n);\n\nexport const CommandGroup = withCn(\n CommandPrimitive.Group,\n 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground'\n);\n\nexport const CommandSeparator = withCn(\n CommandPrimitive.Separator,\n '-mx-1 h-px bg-border'\n);\n\nexport const CommandItem = withCn(\n CommandPrimitive.Item,\n 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50'\n);\n\nexport const CommandShortcut = withCn(\n createPrimitiveElement('span'),\n 'ml-auto text-xs tracking-widest text-muted-foreground'\n);\n", "path": "plate-ui/command.tsx", "target": "components/plate-ui/command.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/copilot-hover-card.json b/apps/www/public/r/styles/default/copilot-hover-card.json new file mode 100644 index 0000000000..7338e5838c --- /dev/null +++ b/apps/www/public/r/styles/default/copilot-hover-card.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "@udecode/plate-ai" + ], + "files": [ + { + "content": "import type { CopilotHoverCardProps } from '@udecode/plate-ai/react';\n\nexport const copilotHoverCard = ({ suggestionText }: CopilotHoverCardProps) => {\n return (\n \n {suggestionText}\n \n );\n};\n", + "path": "plate-ui/ghost-text.tsx", + "target": "", + "type": "registry:ui" + } + ], + "name": "ghost-text", + "registryDependencies": [ + "" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/cursor-overlay.json b/apps/www/public/r/styles/default/cursor-overlay.json index bcd01a5ad1..340677929f 100644 --- a/apps/www/public/r/styles/default/cursor-overlay.json +++ b/apps/www/public/r/styles/default/cursor-overlay.json @@ -2,7 +2,7 @@ "dependencies": [], "files": [ { - "content": "import React from 'react';\n\nimport { cn } from '@udecode/cn';\nimport {\n createPlatePlugin,\n findEventRange,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport {\n type CursorData,\n type CursorOverlayProps,\n type CursorProps,\n type CursorState,\n CursorOverlay as CursorOverlayPrimitive,\n} from '@udecode/plate-cursor';\nimport { DndPlugin } from '@udecode/plate-dnd';\n\nexport function Cursor({\n caretPosition,\n classNames,\n data,\n disableCaret,\n disableSelection,\n selectionRects,\n}: CursorProps) {\n const { style, selectionStyle = style } = data ?? ({} as CursorData);\n\n return (\n <>\n {!disableSelection &&\n selectionRects.map((position, i) => (\n \n ))}\n {!disableCaret && caretPosition && (\n \n )}\n \n );\n}\n\nexport function CursorOverlay({ cursors, ...props }: CursorOverlayProps) {\n const editor = useEditorRef();\n const dynamicCursors = editor.useOption(DragOverCursorPlugin, 'cursors');\n\n const allCursors = { ...cursors, ...dynamicCursors };\n\n return (\n \n );\n}\n\nconst DragOverCursorPlugin = createPlatePlugin({\n key: 'dragOverCursor',\n options: { cursors: {} as Record> },\n handlers: {\n onDragEnd: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n onDragLeave: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n onDragOver: ({ editor, event, plugin }) => {\n if (editor.getOptions(DndPlugin).isDragging) return;\n\n const range = findEventRange(editor, event);\n\n if (!range) return;\n\n editor.setOption(plugin, 'cursors', {\n drag: {\n key: 'drag',\n data: {\n style: {\n backgroundColor: 'hsl(222.2 47.4% 11.2%)',\n width: 3,\n },\n },\n selection: range,\n },\n });\n },\n onDrop: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n },\n});\n", + "content": "import React, { useEffect } from 'react';\n\nimport { cn } from '@udecode/cn';\nimport {\n createPlatePlugin,\n findEventRange,\n useEditorPlugin,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport {\n type CursorData,\n type CursorOverlayProps,\n type CursorProps,\n type CursorState,\n CursorOverlay as CursorOverlayPrimitive,\n} from '@udecode/plate-cursor';\nimport { DndPlugin } from '@udecode/plate-dnd';\nimport { BlockSelectionPlugin } from '@udecode/plate-selection/react';\n\nexport function Cursor({\n caretPosition,\n classNames,\n data,\n disableCaret,\n disableSelection,\n selectionRects,\n}: CursorProps) {\n const { style, selectionStyle = style } = data ?? ({} as CursorData);\n\n return (\n <>\n {!disableSelection &&\n selectionRects.map((position, i) => (\n \n ))}\n {!disableCaret && caretPosition && (\n \n )}\n \n );\n}\n\nexport function CursorOverlay({ cursors, ...props }: CursorOverlayProps) {\n const editor = useEditorRef();\n const dynamicCursors = editor.useOption(DragOverCursorPlugin, 'cursors');\n\n const allCursors = { ...cursors, ...dynamicCursors };\n\n return (\n \n );\n}\n\nconst DragOverCursorPlugin = createPlatePlugin({\n key: 'dragOverCursor',\n options: { cursors: {} as Record> },\n handlers: {\n onDragEnd: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n onDragLeave: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n onDragOver: ({ editor, event, plugin }) => {\n if (editor.getOptions(DndPlugin).isDragging) return;\n\n const range = findEventRange(editor, event);\n\n if (!range) return;\n\n editor.setOption(plugin, 'cursors', {\n drag: {\n key: 'drag',\n data: {\n style: {\n backgroundColor: 'hsl(222.2 47.4% 11.2%)',\n width: 3,\n },\n },\n selection: range,\n },\n });\n },\n onDrop: ({ editor, plugin }) => {\n editor.setOption(plugin, 'cursors', {});\n },\n },\n});\n\nexport const SelectionOverlayPlugin = createPlatePlugin({\n key: 'selection_over_lay',\n useHooks: () => {\n const { editor } = useEditorPlugin(BlockSelectionPlugin);\n const isSelecting = editor.useOptions(BlockSelectionPlugin).isSelecting;\n\n useEffect(() => {\n if (isSelecting) {\n setTimeout(() => {\n editor.setOption(DragOverCursorPlugin, 'cursors', {});\n }, 0);\n }\n }, [editor, isSelecting]);\n },\n handlers: {\n onBlur: ({ editor, event }) => {\n const isPrevented =\n (event.relatedTarget as HTMLElement)?.dataset?.platePreventOverlay ===\n 'true';\n\n if (isPrevented) return;\n if (editor.selection) {\n editor.setOption(DragOverCursorPlugin, 'cursors', {\n drag: {\n key: 'blur',\n data: {\n selectionStyle: {\n backgroundColor: 'rgba(47, 121, 216, 0.35)',\n },\n },\n selection: editor.selection,\n },\n });\n }\n },\n onFocus: ({ editor }) => {\n editor.setOption(DragOverCursorPlugin, 'cursors', {});\n },\n },\n});\n", "path": "plate-ui/cursor-overlay.tsx", "target": "components/plate-ui/cursor-overlay.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/dialog.json b/apps/www/public/r/styles/default/dialog.json index 2bd94ebc5f..d11e00b0cd 100644 --- a/apps/www/public/r/styles/default/dialog.json +++ b/apps/www/public/r/styles/default/dialog.json @@ -1,6 +1,7 @@ { "dependencies": [ - "@radix-ui/react-dialog" + "@radix-ui/react-dialog", + "@radix-ui/react-visually-hidden" ], "files": [ { diff --git a/apps/www/public/r/styles/default/editor.json b/apps/www/public/r/styles/default/editor.json index fb9b1fa831..61ec1cf9df 100644 --- a/apps/www/public/r/styles/default/editor.json +++ b/apps/www/public/r/styles/default/editor.json @@ -2,7 +2,7 @@ "dependencies": [], "files": [ { - "content": "import React from 'react';\n\nimport type { PlateContentProps } from '@udecode/plate-common/react';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@udecode/cn';\nimport { PlateContent } from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\nconst editorVariants = cva(\n cn(\n 'relative overflow-x-auto whitespace-pre-wrap break-words text-foreground',\n 'min-h-[80px] w-full rounded-md bg-background px-6 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none',\n '[&_[data-slate-placeholder]]:text-muted-foreground [&_[data-slate-placeholder]]:!opacity-100',\n '[&_[data-slate-placeholder]]:top-[auto_!important]',\n '[&_strong]:font-bold'\n ),\n {\n defaultVariants: {\n focusRing: true,\n size: 'sm',\n variant: 'outline',\n },\n variants: {\n disabled: {\n true: 'cursor-not-allowed opacity-50',\n },\n focusRing: {\n false: '',\n true: 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n },\n focused: {\n true: 'ring-2 ring-ring ring-offset-2',\n },\n size: {\n md: 'text-base',\n sm: 'text-sm',\n },\n variant: {\n ghost: '',\n outline: 'border border-input',\n },\n },\n }\n);\n\nexport type EditorProps = PlateContentProps &\n VariantProps;\n\nconst Editor = React.forwardRef(\n (\n {\n className,\n disabled,\n focusRing,\n focused,\n readOnly,\n size,\n variant,\n ...props\n },\n ref\n ) => {\n return (\n
\n \n
\n );\n }\n);\nEditor.displayName = 'Editor';\n\nexport { Editor };\n", + "content": "import React from 'react';\n\nimport type { PlateContentProps } from '@udecode/plate-common/react';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@udecode/cn';\nimport { PlateContent } from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\nconst editorVariants = cva(\n cn(\n 'relative overflow-x-auto whitespace-pre-wrap break-words text-foreground',\n 'min-h-[80px] w-full rounded-md bg-background px-6 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none',\n '[&_[data-slate-placeholder]]:text-muted-foreground [&_[data-slate-placeholder]]:!opacity-100',\n '[&_[data-slate-placeholder]]:top-[auto_!important]',\n '[&_strong]:font-bold'\n ),\n {\n defaultVariants: {\n focusRing: true,\n size: 'sm',\n variant: 'outline',\n },\n variants: {\n disabled: {\n true: 'cursor-not-allowed opacity-50',\n },\n focusRing: {\n false: '',\n true: 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n },\n focused: {\n true: 'ring-2 ring-ring ring-offset-2',\n },\n size: {\n md: 'text-base',\n sm: 'text-sm',\n },\n variant: {\n aiChat:\n 'max-h-[min(70vh,320px)] w-full overflow-y-auto rounded-none border-b px-3 py-2 text-sm',\n demo: 'min-h-full w-full px-16 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]',\n ghost: '',\n outline: 'border border-input',\n },\n },\n }\n);\n\nexport type EditorProps = PlateContentProps &\n VariantProps;\n\nconst Editor = React.forwardRef(\n (\n {\n className,\n disabled,\n focusRing,\n focused,\n readOnly,\n size,\n variant,\n ...props\n },\n ref\n ) => {\n return (\n
\n \n
\n );\n }\n);\nEditor.displayName = 'Editor';\n\nexport { Editor };\n", "path": "plate-ui/editor.tsx", "target": "components/plate-ui/editor.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/excalidraw-element.json b/apps/www/public/r/styles/default/excalidraw-element.json index d99e5622b7..7798eb3e9b 100644 --- a/apps/www/public/r/styles/default/excalidraw-element.json +++ b/apps/www/public/r/styles/default/excalidraw-element.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "import React from 'react';\n\nimport { withRef } from '@udecode/cn';\nimport { useExcalidrawElement } from '@udecode/plate-excalidraw/react';\n\nimport { PlateElement } from './plate-element';\n\nexport const ExcalidrawElement = withRef(\n ({ nodeProps, ...props }, ref) => {\n const { children, element } = props;\n\n const { Excalidraw, excalidrawProps } = useExcalidrawElement({\n element,\n });\n\n return (\n \n
\n
\n {Excalidraw && (\n \n )}\n
\n
\n {children}\n
\n );\n }\n);\n", + "content": "'use client';\nimport React from 'react';\n\nimport { withRef } from '@udecode/cn';\nimport { useExcalidrawElement } from '@udecode/plate-excalidraw/react';\n\nimport { PlateElement } from './plate-element';\n\nexport const ExcalidrawElement = withRef(\n ({ nodeProps, ...props }, ref) => {\n const { children, element } = props;\n\n const { Excalidraw, excalidrawProps } = useExcalidrawElement({\n element,\n });\n\n return (\n \n
\n
\n {Excalidraw && (\n \n )}\n
\n
\n {children}\n
\n );\n }\n);\n", "path": "plate-ui/excalidraw-element.tsx", "target": "components/plate-ui/excalidraw-element.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/fixed-toolbar-buttons.json b/apps/www/public/r/styles/default/fixed-toolbar-buttons.json index b7c858254e..e31abcb7e5 100644 --- a/apps/www/public/r/styles/default/fixed-toolbar-buttons.json +++ b/apps/www/public/r/styles/default/fixed-toolbar-buttons.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "import React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n
\n \n {!readOnly && (\n <>\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n )}\n\n
\n\n \n \n \n
\n
\n );\n}\n", + "content": "import React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n
\n \n {!readOnly && (\n <>\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n )}\n\n
\n\n \n \n \n
\n
\n );\n}\n", "path": "plate-ui/fixed-toolbar-buttons.tsx", "target": "components/plate-ui/fixed-toolbar-buttons.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/fixed-toolbar.json b/apps/www/public/r/styles/default/fixed-toolbar.json index 4d671871ce..4df0ebef31 100644 --- a/apps/www/public/r/styles/default/fixed-toolbar.json +++ b/apps/www/public/r/styles/default/fixed-toolbar.json @@ -2,7 +2,7 @@ "dependencies": [], "files": [ { - "content": "import { withCn } from '@udecode/cn';\n\nimport { Toolbar } from './toolbar';\n\nexport const FixedToolbar = withCn(\n Toolbar,\n 'supports-backdrop-blur:bg-background/60 sticky left-0 top-0 z-50 w-full justify-between overflow-x-auto rounded-t-lg border-b border-b-border bg-background/95 backdrop-blur'\n);\n", + "content": "import { withCn } from '@udecode/cn';\n\nimport { Toolbar } from './toolbar';\n\nexport const FixedToolbar = withCn(\n Toolbar,\n 'supports-backdrop-blur:bg-background/60 sticky left-0 top-0 z-50 w-full justify-between overflow-x-auto rounded-t-lg border-b border-b-border bg-background/95 p-1 backdrop-blur'\n);\n", "path": "plate-ui/fixed-toolbar.tsx", "target": "components/plate-ui/fixed-toolbar.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/floating-toolbar-buttons.json b/apps/www/public/r/styles/default/floating-toolbar-buttons.json index 816d375ffd..20eb49045a 100644 --- a/apps/www/public/r/styles/default/floating-toolbar-buttons.json +++ b/apps/www/public/r/styles/default/floating-toolbar-buttons.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "import React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FloatingToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n <>\n {!readOnly && (\n <>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n \n );\n}\n", + "content": "import React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport { ToolbarGroup } from './toolbar';\n\n// import { AIToolbarButton } from './ai-toolbar-button';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FloatingToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n <>\n {!readOnly && (\n <>\n {/* \n \n \n Ask AI\n \n */}\n\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n \n );\n}\n", "path": "plate-ui/floating-toolbar-buttons.tsx", "target": "components/plate-ui/floating-toolbar-buttons.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/floating-toolbar.json b/apps/www/public/r/styles/default/floating-toolbar.json index 4e6e5628e0..42b4e056e2 100644 --- a/apps/www/public/r/styles/default/floating-toolbar.json +++ b/apps/www/public/r/styles/default/floating-toolbar.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "'use client';\n\nimport React from 'react';\n\nimport { cn, withRef } from '@udecode/cn';\nimport {\n useComposedRef,\n useEditorId,\n useEditorRef,\n useEventEditorSelectors,\n} from '@udecode/plate-common/react';\nimport {\n type FloatingToolbarState,\n flip,\n offset,\n useFloatingToolbar,\n useFloatingToolbarState,\n} from '@udecode/plate-floating';\nimport { LinkPlugin } from '@udecode/plate-link/react';\n\nimport { Toolbar } from './toolbar';\n\nexport const FloatingToolbar = withRef<\n typeof Toolbar,\n {\n state?: FloatingToolbarState;\n }\n>(({ children, state, ...props }, componentRef) => {\n const editor = useEditorRef();\n const editorId = useEditorId();\n const focusedEditorId = useEventEditorSelectors.focus();\n const isFloatingLinkOpen = !!editor.useOption(LinkPlugin, 'mode');\n\n const floatingToolbarState = useFloatingToolbarState({\n editorId,\n focusedEditorId,\n hideToolbar: isFloatingLinkOpen,\n ...state,\n floatingOptions: {\n middleware: [\n offset(12),\n flip({\n fallbackPlacements: [\n 'top-start',\n 'top-end',\n 'bottom-start',\n 'bottom-end',\n ],\n padding: 12,\n }),\n ],\n placement: 'top',\n ...state?.floatingOptions,\n },\n });\n\n const {\n clickOutsideRef,\n hidden,\n props: rootProps,\n ref: floatingRef,\n } = useFloatingToolbar(floatingToolbarState);\n\n const ref = useComposedRef(componentRef, floatingRef);\n\n if (hidden) return null;\n\n return (\n
\n \n {children}\n \n
\n );\n});\n", + "content": "'use client';\n\nimport React from 'react';\n\nimport { cn, withRef } from '@udecode/cn';\nimport {\n useComposedRef,\n useEditorId,\n useEditorRef,\n useEventEditorSelectors,\n} from '@udecode/plate-common/react';\nimport {\n type FloatingToolbarState,\n flip,\n offset,\n useFloatingToolbar,\n useFloatingToolbarState,\n} from '@udecode/plate-floating';\n\nimport { Toolbar } from './toolbar';\n\nexport const FloatingToolbar = withRef<\n typeof Toolbar,\n {\n state?: FloatingToolbarState;\n }\n>(({ children, state, ...props }, componentRef) => {\n const editor = useEditorRef();\n const editorId = useEditorId();\n const focusedEditorId = useEventEditorSelectors.focus();\n const isFloatingLinkOpen = !!editor.useOption({ key: 'a' }, 'mode');\n const isAIChatOpen = editor.useOption({ key: 'aiChat' }, 'open');\n\n const floatingToolbarState = useFloatingToolbarState({\n editorId,\n focusedEditorId,\n hideToolbar: isFloatingLinkOpen || isAIChatOpen,\n ...state,\n floatingOptions: {\n middleware: [\n offset(12),\n flip({\n fallbackPlacements: [\n 'top-start',\n 'top-end',\n 'bottom-start',\n 'bottom-end',\n ],\n padding: 12,\n }),\n ],\n placement: 'top',\n ...state?.floatingOptions,\n },\n });\n\n const {\n clickOutsideRef,\n hidden,\n props: rootProps,\n ref: floatingRef,\n } = useFloatingToolbar(floatingToolbarState);\n\n const ref = useComposedRef(componentRef, floatingRef);\n\n if (hidden) return null;\n\n return (\n
\n \n {children}\n \n
\n );\n});\n", "path": "plate-ui/floating-toolbar.tsx", "target": "components/plate-ui/floating-toolbar.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/ghost-text.json b/apps/www/public/r/styles/default/ghost-text.json new file mode 100644 index 0000000000..2139c922f2 --- /dev/null +++ b/apps/www/public/r/styles/default/ghost-text.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "@udecode/plate-ai" + ], + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\n\nimport type { CopilotPluginConfig } from '@udecode/plate-ai/react';\n\nimport { useEditorPlugin, useElement } from '@udecode/plate-common/react';\n\nexport const GhostText = () => {\n const { useOption } = useEditorPlugin({\n key: 'copilot',\n });\n const element = useElement();\n\n const isSuggested = useOption('isSuggested', element.id as string);\n\n if (!isSuggested) return null;\n\n return ;\n};\n\nexport function GhostTextContent() {\n const { useOption } = useEditorPlugin({\n key: 'copilot',\n });\n\n const suggestionText = useOption('suggestionText');\n\n return (\n \n {suggestionText && suggestionText}\n \n );\n}\n", + "path": "plate-ui/ghost-text.tsx", + "target": "components/plate-ui/ghost-text.tsx", + "type": "registry:ui" + } + ], + "name": "ghost-text", + "registryDependencies": [ + "" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/heading-element.json b/apps/www/public/r/styles/default/heading-element.json index 3b0b7b58db..401c51860e 100644 --- a/apps/www/public/r/styles/default/heading-element.json +++ b/apps/www/public/r/styles/default/heading-element.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "import React from 'react';\n\nimport { withRef, withVariants } from '@udecode/cn';\nimport { cva } from 'class-variance-authority';\n\nimport { PlateElement } from './plate-element';\n\nconst headingVariants = cva('relative mb-1', {\n variants: {\n variant: {\n h1: 'mt-[1.6em] pb-1 font-heading text-4xl font-bold',\n h2: 'mt-[1.4em] pb-px font-heading text-2xl font-semibold tracking-tight',\n h3: 'mt-[1em] pb-px font-heading text-xl font-semibold tracking-tight',\n h4: 'mt-[0.75em] font-heading text-lg font-semibold tracking-tight',\n h5: 'mt-[0.75em] text-lg font-semibold tracking-tight',\n h6: 'mt-[0.75em] text-base font-semibold tracking-tight',\n },\n },\n});\n\nconst blockVariants = cva('', {\n variants: {\n isFirstBlock: {\n false: '',\n true: 'mt-0',\n },\n },\n});\n\nconst HeadingElementVariants = withVariants(\n withVariants(PlateElement, headingVariants, ['variant']),\n blockVariants,\n ['isFirstBlock']\n);\n\nexport const HeadingElement = withRef(\n ({ children, isFirstBlock, variant = 'h1', ...props }, ref) => {\n const { editor, element } = props;\n\n return (\n \n {children}\n \n );\n }\n);\n", + "content": "import React from 'react';\n\nimport { withRef, withVariants } from '@udecode/cn';\nimport { cva } from 'class-variance-authority';\n\nimport { PlateElement } from './plate-element';\n\nconst headingVariants = cva('relative mb-1', {\n variants: {\n variant: {\n h1: 'mt-[1.6em] pb-1 font-heading text-4xl font-bold',\n h2: 'mt-[1.4em] pb-px font-heading text-2xl font-semibold tracking-tight',\n h3: 'mt-[1em] pb-px font-heading text-xl font-semibold tracking-tight',\n h4: 'mt-[0.75em] font-heading text-lg font-semibold tracking-tight',\n h5: 'mt-[0.75em] text-lg font-semibold tracking-tight',\n h6: 'mt-[0.75em] text-base font-semibold tracking-tight',\n },\n },\n});\n\nconst HeadingElementVariants = withVariants(PlateElement, headingVariants, [\n 'variant',\n]);\n\nexport const HeadingElement = withRef(\n ({ children, variant = 'h1', ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n", "path": "plate-ui/heading-element.tsx", "target": "components/plate-ui/heading-element.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/indent-todo-marker.json b/apps/www/public/r/styles/default/indent-todo-marker.json new file mode 100644 index 0000000000..ec4feb7236 --- /dev/null +++ b/apps/www/public/r/styles/default/indent-todo-marker.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "@udecode/plate-indent-list" + ], + "files": [ + { + "content": "import type { PlateRenderElementProps } from '@udecode/plate-common/react';\n\nimport { cn } from '@udecode/cn';\nimport {\n useIndentTodoListElement,\n useIndentTodoListElementState,\n} from '@udecode/plate-indent-list/react';\n\nimport { Checkbox } from './checkbox';\n\nexport const TodoMarker = ({\n element,\n}: Omit) => {\n const state = useIndentTodoListElementState({ element });\n const { checkboxProps } = useIndentTodoListElement(state);\n\n return (\n
\n \n
\n );\n};\n\nexport const TodoLi = (props: PlateRenderElementProps) => {\n const { children, element } = props;\n\n return (\n \n {children}\n \n );\n};\n", + "path": "plate-ui/indent-todo-marker.tsx", + "target": "components/plate-ui/indent-todo-marker.tsx", + "type": "registry:ui" + } + ], + "name": "indent-todo-marker", + "registryDependencies": [ + "checkbox" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/inline-combobox.json b/apps/www/public/r/styles/default/inline-combobox.json index e75ce0a50d..2ee9227568 100644 --- a/apps/www/public/r/styles/default/inline-combobox.json +++ b/apps/www/public/r/styles/default/inline-combobox.json @@ -5,7 +5,7 @@ ], "files": [ { - "content": "import React, {\n type HTMLAttributes,\n type ReactNode,\n type RefObject,\n createContext,\n forwardRef,\n startTransition,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport type { PointRef } from 'slate';\n\nimport {\n type ComboboxItemProps,\n Combobox,\n ComboboxItem,\n ComboboxPopover,\n ComboboxProvider,\n Portal,\n useComboboxContext,\n useComboboxStore,\n} from '@ariakit/react';\nimport { cn } from '@udecode/cn';\nimport { filterWords } from '@udecode/plate-combobox';\nimport {\n type UseComboboxInputResult,\n useComboboxInput,\n useHTMLInputCursorState,\n} from '@udecode/plate-combobox/react';\nimport {\n type TElement,\n createPointRef,\n getPointBefore,\n insertText,\n moveSelection,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useComposedRef,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\ntype FilterFn = (\n item: { value: string; keywords?: string[] },\n search: string\n) => boolean;\n\ninterface InlineComboboxContextValue {\n filter: FilterFn | false;\n inputProps: UseComboboxInputResult['props'];\n inputRef: RefObject;\n removeInput: UseComboboxInputResult['removeInput'];\n setHasEmpty: (hasEmpty: boolean) => void;\n showTrigger: boolean;\n trigger: string;\n}\n\nconst InlineComboboxContext = createContext(\n null as any\n);\n\nexport const defaultFilter: FilterFn = ({ keywords = [], value }, search) =>\n [value, ...keywords].some((keyword) => filterWords(keyword, search));\n\ninterface InlineComboboxProps {\n children: ReactNode;\n element: TElement;\n trigger: string;\n filter?: FilterFn | false;\n hideWhenNoValue?: boolean;\n setValue?: (value: string) => void;\n showTrigger?: boolean;\n value?: string;\n}\n\nconst InlineCombobox = ({\n children,\n element,\n filter = defaultFilter,\n hideWhenNoValue = false,\n setValue: setValueProp,\n showTrigger = true,\n trigger,\n value: valueProp,\n}: InlineComboboxProps) => {\n const editor = useEditorRef();\n const inputRef = React.useRef(null);\n const cursorState = useHTMLInputCursorState(inputRef);\n\n const [valueState, setValueState] = useState('');\n const hasValueProp = valueProp !== undefined;\n const value = hasValueProp ? valueProp : valueState;\n\n const setValue = useCallback(\n (newValue: string) => {\n setValueProp?.(newValue);\n\n if (!hasValueProp) {\n setValueState(newValue);\n }\n },\n [setValueProp, hasValueProp]\n );\n\n /**\n * Track the point just before the input element so we know where to\n * insertText if the combobox closes due to a selection change.\n */\n const [insertPoint, setInsertPoint] = useState(null);\n\n useEffect(() => {\n const path = findNodePath(editor, element);\n\n if (!path) return;\n\n const point = getPointBefore(editor, path);\n\n if (!point) return;\n\n const pointRef = createPointRef(editor, point);\n setInsertPoint(pointRef);\n\n return () => {\n pointRef.unref();\n };\n }, [editor, element]);\n\n const { props: inputProps, removeInput } = useComboboxInput({\n cancelInputOnBlur: false,\n cursorState,\n ref: inputRef,\n onCancelInput: (cause) => {\n if (cause !== 'backspace') {\n insertText(editor, trigger + value, {\n at: insertPoint?.current ?? undefined,\n });\n }\n if (cause === 'arrowLeft' || cause === 'arrowRight') {\n moveSelection(editor, {\n distance: 1,\n reverse: cause === 'arrowLeft',\n });\n }\n },\n });\n\n const [hasEmpty, setHasEmpty] = useState(false);\n\n const contextValue: InlineComboboxContextValue = useMemo(\n () => ({\n filter,\n inputProps,\n inputRef,\n removeInput,\n setHasEmpty,\n showTrigger,\n trigger,\n }),\n [\n trigger,\n showTrigger,\n filter,\n inputRef,\n inputProps,\n removeInput,\n setHasEmpty,\n ]\n );\n\n const store = useComboboxStore({\n // open: ,\n setValue: (newValue) => startTransition(() => setValue(newValue)),\n });\n\n const items = store.useState('items');\n\n /**\n * If there is no active ID and the list of items changes, select the first\n * item.\n */\n useEffect(() => {\n if (!store.getState().activeId) {\n store.setActiveId(store.first());\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [items, store]);\n\n return (\n \n 0 || hasEmpty) &&\n (!hideWhenNoValue || value.length > 0)\n }\n store={store}\n >\n \n {children}\n \n \n \n );\n};\n\nconst InlineComboboxInput = forwardRef<\n HTMLInputElement,\n HTMLAttributes\n>(({ className, ...props }, propRef) => {\n const {\n inputProps,\n inputRef: contextRef,\n showTrigger,\n trigger,\n } = useContext(InlineComboboxContext);\n\n const store = useComboboxContext()!;\n const value = store.useState('value');\n\n const ref = useComposedRef(propRef, contextRef);\n\n /**\n * To create an auto-resizing input, we render a visually hidden span\n * containing the input value and position the input element on top of it.\n * This works well for all cases except when input exceeds the width of the\n * container.\n */\n\n return (\n <>\n {showTrigger && trigger}\n\n \n \n {value || '\\u200B'}\n \n\n \n \n \n );\n});\n\nInlineComboboxInput.displayName = 'InlineComboboxInput';\n\nconst InlineComboboxContent: typeof ComboboxPopover = ({\n className,\n ...props\n}) => {\n // Portal prevents CSS from leaking into popover\n return (\n \n \n \n );\n};\n\nconst comboboxItemVariants = cva(\n 'relative flex h-9 select-none items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none',\n {\n defaultVariants: {\n interactive: true,\n },\n variants: {\n interactive: {\n false: '',\n true: 'cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground data-[active-item=true]:bg-accent data-[active-item=true]:text-accent-foreground',\n },\n },\n }\n);\n\nexport type InlineComboboxItemProps = {\n keywords?: string[];\n} & ComboboxItemProps &\n Required>;\n\nconst InlineComboboxItem = ({\n className,\n keywords,\n onClick,\n ...props\n}: InlineComboboxItemProps) => {\n const { value } = props;\n\n const { filter, removeInput } = useContext(InlineComboboxContext);\n\n const store = useComboboxContext()!;\n\n // Optimization: Do not subscribe to value if filter is false\n const search = filter && store.useState('value');\n\n const visible = useMemo(\n () => !filter || filter({ keywords, value }, search as string),\n [filter, value, keywords, search]\n );\n\n if (!visible) return null;\n\n return (\n {\n removeInput(true);\n onClick?.(event);\n }}\n {...props}\n />\n );\n};\n\nconst InlineComboboxEmpty = ({\n children,\n className,\n}: HTMLAttributes) => {\n const { setHasEmpty } = useContext(InlineComboboxContext);\n const store = useComboboxContext()!;\n const items = store.useState('items');\n\n useEffect(() => {\n setHasEmpty(true);\n\n return () => {\n setHasEmpty(false);\n };\n }, [setHasEmpty]);\n\n if (items.length > 0) return null;\n\n return (\n \n {children}\n
\n );\n};\n\nexport {\n InlineCombobox,\n InlineComboboxContent,\n InlineComboboxEmpty,\n InlineComboboxInput,\n InlineComboboxItem,\n};\n", + "content": "import React, {\n type HTMLAttributes,\n type ReactNode,\n type RefObject,\n createContext,\n forwardRef,\n startTransition,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport type { PointRef } from 'slate';\n\nimport {\n type ComboboxItemProps,\n Combobox,\n ComboboxItem,\n ComboboxPopover,\n ComboboxProvider,\n Portal,\n useComboboxContext,\n useComboboxStore,\n} from '@ariakit/react';\nimport { cn } from '@udecode/cn';\nimport { filterWords } from '@udecode/plate-combobox';\nimport {\n type UseComboboxInputResult,\n useComboboxInput,\n useHTMLInputCursorState,\n} from '@udecode/plate-combobox/react';\nimport {\n type TElement,\n createPointRef,\n getPointBefore,\n insertText,\n moveSelection,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useComposedRef,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\ntype FilterFn = (\n item: { value: string; keywords?: string[] },\n search: string\n) => boolean;\n\ninterface InlineComboboxContextValue {\n filter: FilterFn | false;\n inputProps: UseComboboxInputResult['props'];\n inputRef: RefObject;\n removeInput: UseComboboxInputResult['removeInput'];\n setHasEmpty: (hasEmpty: boolean) => void;\n showTrigger: boolean;\n trigger: string;\n}\n\nconst InlineComboboxContext = createContext(\n null as any\n);\n\nexport const defaultFilter: FilterFn = ({ keywords = [], value }, search) =>\n [value, ...keywords].some((keyword) => filterWords(keyword, search));\n\ninterface InlineComboboxProps {\n children: ReactNode;\n element: TElement;\n trigger: string;\n filter?: FilterFn | false;\n hideWhenNoValue?: boolean;\n setValue?: (value: string) => void;\n showTrigger?: boolean;\n value?: string;\n}\n\nconst InlineCombobox = ({\n children,\n element,\n filter = defaultFilter,\n hideWhenNoValue = false,\n setValue: setValueProp,\n showTrigger = true,\n trigger,\n value: valueProp,\n}: InlineComboboxProps) => {\n const editor = useEditorRef();\n const inputRef = React.useRef(null);\n const cursorState = useHTMLInputCursorState(inputRef);\n\n const [valueState, setValueState] = useState('');\n const hasValueProp = valueProp !== undefined;\n const value = hasValueProp ? valueProp : valueState;\n\n const setValue = useCallback(\n (newValue: string) => {\n setValueProp?.(newValue);\n\n if (!hasValueProp) {\n setValueState(newValue);\n }\n },\n [setValueProp, hasValueProp]\n );\n\n /**\n * Track the point just before the input element so we know where to\n * insertText if the combobox closes due to a selection change.\n */\n const [insertPoint, setInsertPoint] = useState(null);\n\n useEffect(() => {\n const path = findNodePath(editor, element);\n\n if (!path) return;\n\n const point = getPointBefore(editor, path);\n\n if (!point) return;\n\n const pointRef = createPointRef(editor, point);\n setInsertPoint(pointRef);\n\n return () => {\n pointRef.unref();\n };\n }, [editor, element]);\n\n const { props: inputProps, removeInput } = useComboboxInput({\n cancelInputOnBlur: false,\n cursorState,\n ref: inputRef,\n onCancelInput: (cause) => {\n if (cause !== 'backspace') {\n insertText(editor, trigger + value, {\n at: insertPoint?.current ?? undefined,\n });\n }\n if (cause === 'arrowLeft' || cause === 'arrowRight') {\n moveSelection(editor, {\n distance: 1,\n reverse: cause === 'arrowLeft',\n });\n }\n },\n });\n\n const [hasEmpty, setHasEmpty] = useState(false);\n\n const contextValue: InlineComboboxContextValue = useMemo(\n () => ({\n filter,\n inputProps,\n inputRef,\n removeInput,\n setHasEmpty,\n showTrigger,\n trigger,\n }),\n [\n trigger,\n showTrigger,\n filter,\n inputRef,\n inputProps,\n removeInput,\n setHasEmpty,\n ]\n );\n\n const store = useComboboxStore({\n // open: ,\n setValue: (newValue) => startTransition(() => setValue(newValue)),\n });\n\n const items = store.useState('items');\n\n /**\n * If there is no active ID and the list of items changes, select the first\n * item.\n */\n useEffect(() => {\n if (!store.getState().activeId) {\n store.setActiveId(store.first());\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [items, store]);\n\n return (\n \n 0 || hasEmpty) &&\n (!hideWhenNoValue || value.length > 0)\n }\n store={store}\n >\n \n {children}\n \n \n \n );\n};\n\nconst InlineComboboxInput = forwardRef<\n HTMLInputElement,\n HTMLAttributes\n>(({ className, ...props }, propRef) => {\n const {\n inputProps,\n inputRef: contextRef,\n showTrigger,\n trigger,\n } = useContext(InlineComboboxContext);\n\n const store = useComboboxContext()!;\n const value = store.useState('value');\n\n const ref = useComposedRef(propRef, contextRef);\n\n /**\n * To create an auto-resizing input, we render a visually hidden span\n * containing the input value and position the input element on top of it.\n * This works well for all cases except when input exceeds the width of the\n * container.\n */\n\n return (\n <>\n {showTrigger && trigger}\n\n \n \n {value || '\\u200B'}\n \n\n \n \n \n );\n});\n\nInlineComboboxInput.displayName = 'InlineComboboxInput';\n\nconst InlineComboboxContent: typeof ComboboxPopover = ({\n className,\n ...props\n}) => {\n // Portal prevents CSS from leaking into popover\n return (\n \n \n \n );\n};\n\nconst comboboxItemVariants = cva(\n 'relative flex h-9 select-none items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none',\n {\n defaultVariants: {\n interactive: true,\n },\n variants: {\n interactive: {\n false: '',\n true: 'cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground data-[active-item=true]:bg-accent data-[active-item=true]:text-accent-foreground',\n },\n },\n }\n);\n\nexport type InlineComboboxItemProps = {\n focusEditor?: boolean;\n keywords?: string[];\n} & ComboboxItemProps &\n Required>;\n\nconst InlineComboboxItem = ({\n className,\n focusEditor = true,\n keywords,\n onClick,\n ...props\n}: InlineComboboxItemProps) => {\n const { value } = props;\n\n const { filter, removeInput } = useContext(InlineComboboxContext);\n\n const store = useComboboxContext()!;\n\n // Optimization: Do not subscribe to value if filter is false\n const search = filter && store.useState('value');\n\n const visible = useMemo(\n () => !filter || filter({ keywords, value }, search as string),\n [filter, value, keywords, search]\n );\n\n if (!visible) return null;\n\n return (\n {\n removeInput(focusEditor);\n onClick?.(event);\n }}\n {...props}\n />\n );\n};\n\nconst InlineComboboxEmpty = ({\n children,\n className,\n}: HTMLAttributes) => {\n const { setHasEmpty } = useContext(InlineComboboxContext);\n const store = useComboboxContext()!;\n const items = store.useState('items');\n\n useEffect(() => {\n setHasEmpty(true);\n\n return () => {\n setHasEmpty(false);\n };\n }, [setHasEmpty]);\n\n if (items.length > 0) return null;\n\n return (\n \n {children}\n \n );\n};\n\nexport {\n InlineCombobox,\n InlineComboboxContent,\n InlineComboboxEmpty,\n InlineComboboxInput,\n InlineComboboxItem,\n};\n", "path": "plate-ui/inline-combobox.tsx", "target": "components/plate-ui/inline-combobox.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/mode-dropdown-menu.json b/apps/www/public/r/styles/default/mode-dropdown-menu.json index 736795610b..a9be79e5fa 100644 --- a/apps/www/public/r/styles/default/mode-dropdown-menu.json +++ b/apps/www/public/r/styles/default/mode-dropdown-menu.json @@ -2,7 +2,7 @@ "dependencies": [], "files": [ { - "content": "import React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport {\n focusEditor,\n useEditorReadOnly,\n useEditorRef,\n usePlateStore,\n} from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\nexport function ModeDropdownMenu(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const setReadOnly = usePlateStore().set.readOnly();\n const readOnly = useEditorReadOnly();\n const openState = useOpenState();\n\n let value = 'editing';\n\n if (readOnly) value = 'viewing';\n\n const item: any = {\n editing: (\n <>\n \n Editing\n \n ),\n viewing: (\n <>\n \n Viewing\n \n ),\n };\n\n return (\n \n \n \n {item[value]}\n \n \n\n \n {\n if (newValue !== 'viewing') {\n setReadOnly(false);\n }\n if (newValue === 'viewing') {\n setReadOnly(true);\n\n return;\n }\n if (newValue === 'editing') {\n focusEditor(editor);\n\n return;\n }\n }}\n >\n \n {item.editing}\n \n\n \n {item.viewing}\n \n \n \n \n );\n}\n", + "content": "import React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport {\n focusEditor,\n useEditorReadOnly,\n useEditorRef,\n usePlateStore,\n} from '@udecode/plate-common/react';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\nexport function ModeDropdownMenu(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const setReadOnly = usePlateStore().set.readOnly();\n const readOnly = useEditorReadOnly();\n const openState = useOpenState();\n\n let value = 'editing';\n\n if (readOnly) value = 'viewing';\n\n const item: any = {\n editing: (\n <>\n \n Editing\n \n ),\n viewing: (\n <>\n \n Viewing\n \n ),\n };\n\n return (\n \n \n \n {item[value]}\n \n \n\n \n {\n if (newValue !== 'viewing') {\n setReadOnly(false);\n }\n if (newValue === 'viewing') {\n setReadOnly(true);\n\n return;\n }\n if (newValue === 'editing') {\n focusEditor(editor);\n\n return;\n }\n }}\n >\n \n {item.editing}\n \n\n \n {item.viewing}\n \n \n \n \n );\n}\n", "path": "plate-ui/mode-dropdown-menu.tsx", "target": "components/plate-ui/mode-dropdown-menu.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/mode-toggle.json b/apps/www/public/r/styles/default/mode-toggle.json index 52956389a4..ea46db482e 100644 --- a/apps/www/public/r/styles/default/mode-toggle.json +++ b/apps/www/public/r/styles/default/mode-toggle.json @@ -1,7 +1,7 @@ { "files": [ { - "content": "'use client';\n\nimport * as React from 'react';\n\nimport { useTheme } from 'next-themes';\n\nimport { Icons } from '@/components/icons';\nimport { Button } from '@/components/plate-ui/button';\n\nexport default function ModeToggle() {\n const { setTheme, theme } = useTheme();\n\n return (\n setTheme(theme === 'dark' ? 'light' : 'dark')}\n >\n {theme === 'dark' ? (\n \n ) : (\n \n )}\n Toggle theme\n \n );\n}\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport { useTheme } from 'next-themes';\n\nimport { Icons } from '@/components/icons';\nimport { useMounted } from '@/hooks/use-mounted';\nimport { Button } from '@/components/plate-ui/button';\n\nexport default function ModeToggle() {\n const { setTheme, theme } = useTheme();\n\n const mounted = useMounted();\n\n return (\n setTheme(theme === 'dark' ? 'light' : 'dark')}\n >\n {mounted && theme === 'dark' ? (\n \n ) : (\n \n )}\n Toggle theme\n \n );\n}\n", "path": "example/mode-toggle.tsx", "target": "components/mode-toggle.tsx", "type": "registry:example" diff --git a/apps/www/public/r/styles/default/playground-demo.json b/apps/www/public/r/styles/default/playground-demo.json index 8f4a70f721..6760903ca7 100644 --- a/apps/www/public/r/styles/default/playground-demo.json +++ b/apps/www/public/r/styles/default/playground-demo.json @@ -1,7 +1,7 @@ { "files": [ { - "content": "'use client';\n\nimport React, { useRef } from 'react';\nimport { DndProvider } from 'react-dnd';\nimport { HTML5Backend } from 'react-dnd-html5-backend';\n\nimport type { ValueId } from '@/config/customizer-plugins';\n\nimport { cn } from '@udecode/cn';\nimport { AlignPlugin } from '@udecode/plate-alignment/react';\nimport { AutoformatPlugin } from '@udecode/plate-autoformat/react';\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n SubscriptPlugin,\n SuperscriptPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { SingleLinePlugin } from '@udecode/plate-break/react';\nimport { CaptionPlugin } from '@udecode/plate-caption/react';\nimport { CodeBlockPlugin } from '@udecode/plate-code-block/react';\nimport { CommentsPlugin } from '@udecode/plate-comments/react';\nimport {\n ParagraphPlugin,\n Plate,\n usePlateEditor,\n} from '@udecode/plate-common/react';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { DndPlugin } from '@udecode/plate-dnd';\nimport { DocxPlugin } from '@udecode/plate-docx';\nimport { EmojiPlugin } from '@udecode/plate-emoji/react';\nimport { ExcalidrawPlugin } from '@udecode/plate-excalidraw/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n FontSizePlugin,\n} from '@udecode/plate-font/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { HeadingPlugin, TocPlugin } from '@udecode/plate-heading/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';\nimport { IndentPlugin } from '@udecode/plate-indent/react';\nimport { IndentListPlugin } from '@udecode/plate-indent-list/react';\nimport { JuicePlugin } from '@udecode/plate-juice';\nimport { KbdPlugin } from '@udecode/plate-kbd/react';\nimport { ColumnPlugin } from '@udecode/plate-layout/react';\nimport { LineHeightPlugin } from '@udecode/plate-line-height/react';\nimport { LinkPlugin } from '@udecode/plate-link/react';\nimport { ListPlugin, TodoListPlugin } from '@udecode/plate-list/react';\nimport { MarkdownPlugin } from '@udecode/plate-markdown';\nimport { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react';\nimport { MentionPlugin } from '@udecode/plate-mention/react';\nimport { NodeIdPlugin } from '@udecode/plate-node-id';\nimport { NormalizeTypesPlugin } from '@udecode/plate-normalizers';\nimport { PlaywrightPlugin } from '@udecode/plate-playwright';\nimport { DeletePlugin, SelectOnBackspacePlugin } from '@udecode/plate-select';\nimport { BlockSelectionPlugin } from '@udecode/plate-selection/react';\nimport { SlashPlugin } from '@udecode/plate-slash-command/react';\nimport { TablePlugin } from '@udecode/plate-table/react';\nimport { TogglePlugin } from '@udecode/plate-toggle/react';\nimport { TrailingBlockPlugin } from '@udecode/plate-trailing-block';\nimport Prism from 'prismjs';\n\nimport { CheckPlugin } from '@/components/context/check-plugin';\nimport { settingsStore } from '@/components/context/settings-store';\nimport { PlaygroundFixedToolbarButtons } from '@/components/plate-ui/playground-fixed-toolbar-buttons';\nimport { PlaygroundFloatingToolbarButtons } from '@/components/plate-ui/playground-floating-toolbar-buttons';\nimport { getAutoformatOptions } from '@/lib/plate/demo/plugins/autoformatOptions';\nimport { createPlateUI } from '@/plate/create-plate-ui';\nimport { editableProps } from '@/plate/demo/editableProps';\nimport { isEnabled } from '@/plate/demo/is-enabled';\nimport { DragOverCursorPlugin } from '@/plate/demo/plugins/DragOverCursorPlugin';\nimport { exitBreakPlugin } from '@/plate/demo/plugins/exitBreakPlugin';\nimport { resetBlockTypePlugin } from '@/plate/demo/plugins/resetBlockTypePlugin';\nimport { softBreakPlugin } from '@/plate/demo/plugins/softBreakPlugin';\nimport { tabbablePlugin } from '@/plate/demo/plugins/tabbablePlugin';\nimport { commentsData, usersData } from '@/plate/demo/values/commentsValue';\nimport { usePlaygroundValue } from '@/plate/demo/values/usePlaygroundValue';\nimport { CommentsPopover } from '@/components/plate-ui/comments-popover';\nimport { CursorOverlay } from '@/components/plate-ui/cursor-overlay';\nimport { Editor } from '@/components/plate-ui/editor';\nimport { FixedToolbar } from '@/components/plate-ui/fixed-toolbar';\nimport { FloatingToolbar } from '@/components/plate-ui/floating-toolbar';\nimport { ImagePreview } from '@/components/plate-ui/image-preview';\nimport {\n FireLiComponent,\n FireMarker,\n} from '@/components/plate-ui/indent-fire-marker-component';\nimport {\n TodoLi,\n TodoMarker,\n} from '@/components/plate-ui/indent-todo-marker-component';\nimport { LinkFloatingToolbar } from '@/components/plate-ui/link-floating-toolbar';\n\nimport { usePlaygroundEnabled } from './usePlaygroundEnabled';\n\nexport const usePlaygroundEditor = (id: any = '', scrollSelector?: string) => {\n const enabledPlugins = settingsStore.use.checkedPlugins();\n const overridePlugins = usePlaygroundEnabled(id);\n const autoformatOptions = getAutoformatOptions(id, enabledPlugins);\n\n const value = usePlaygroundValue(id);\n const key = settingsStore.use.version();\n\n const editorId = id || 'playground-' + key;\n\n const a = usePlateEditor(\n {\n id: editorId,\n override: {\n components: createPlateUI({\n draggable: isEnabled('dnd', id),\n placeholder: isEnabled('placeholder', id),\n }),\n plugins: overridePlugins,\n },\n plugins: [\n // Nodes\n HeadingPlugin,\n TocPlugin.configure({\n options: {\n isScroll: true,\n scrollContainerSelector: `#${scrollSelector}`,\n topOffset: 80,\n },\n }),\n BlockquotePlugin,\n CodeBlockPlugin.configure({\n options: {\n prism: Prism,\n },\n }),\n HorizontalRulePlugin,\n LinkPlugin.extend({\n render: { afterEditable: () => },\n }),\n ListPlugin,\n ImagePlugin.extend({\n render: { afterEditable: ImagePreview },\n }),\n MediaEmbedPlugin,\n CaptionPlugin.configure({\n options: {\n plugins: [ImagePlugin, MediaEmbedPlugin],\n },\n }),\n DatePlugin,\n MentionPlugin.configure({\n options: {\n triggerPreviousCharPattern: /^$|^[\\s\"']$/,\n },\n }),\n SlashPlugin,\n TablePlugin.configure({\n options: {\n enableMerging: id === 'tableMerge',\n },\n }),\n TodoListPlugin,\n TogglePlugin,\n ExcalidrawPlugin,\n\n // Marks\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n StrikethroughPlugin,\n CodePlugin,\n SubscriptPlugin,\n SuperscriptPlugin,\n FontColorPlugin,\n FontBackgroundColorPlugin,\n FontSizePlugin,\n HighlightPlugin,\n KbdPlugin,\n\n // Block Style\n AlignPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n MediaEmbedPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n ImagePlugin.key,\n HEADING_KEYS.h6,\n ],\n },\n }),\n IndentPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n BlockquotePlugin.key,\n CodeBlockPlugin.key,\n TogglePlugin.key,\n ],\n },\n }),\n IndentListPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n BlockquotePlugin.key,\n CodeBlockPlugin.key,\n TogglePlugin.key,\n ],\n },\n options: {\n listStyleTypes: {\n fire: {\n liComponent: FireLiComponent,\n markerComponent: FireMarker,\n type: 'fire',\n },\n todo: {\n liComponent: TodoLi,\n markerComponent: TodoMarker,\n type: 'todo',\n },\n },\n },\n }),\n LineHeightPlugin.extend({\n inject: {\n nodeProps: {\n defaultNodeValue: 1.5,\n validNodeValues: [1, 1.2, 1.5, 2, 3],\n },\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n ],\n },\n }),\n\n // Functionality\n AutoformatPlugin.configure({ options: autoformatOptions }),\n BlockSelectionPlugin.configure({\n enabled: !!scrollSelector,\n options: {\n areaOptions: {\n boundaries: `#${scrollSelector}`,\n container: `#${scrollSelector}`,\n selectables: [`#${scrollSelector} .slate-selectable`],\n selectionAreaClass: 'slate-selection-area',\n },\n enableContextMenu: false,\n },\n }),\n DndPlugin.configure({ options: { enableScroller: true } }),\n EmojiPlugin,\n exitBreakPlugin,\n NodeIdPlugin,\n NormalizeTypesPlugin.configure({\n options: {\n rules: [{ path: [0], strictType: HEADING_KEYS.h1 }],\n },\n }),\n resetBlockTypePlugin,\n SelectOnBackspacePlugin.configure({\n options: {\n query: {\n allow: [ImagePlugin.key, HorizontalRulePlugin.key],\n },\n },\n }),\n DeletePlugin,\n SingleLinePlugin,\n softBreakPlugin,\n tabbablePlugin,\n TrailingBlockPlugin.configure({\n options: { type: ParagraphPlugin.key },\n }),\n DragOverCursorPlugin,\n\n // Collaboration\n CommentsPlugin.configure({\n options: {\n comments: commentsData,\n myUserId: '1',\n users: usersData,\n },\n }),\n\n // Deserialization\n DocxPlugin,\n MarkdownPlugin,\n JuicePlugin,\n ColumnPlugin,\n\n // Testing\n PlaywrightPlugin.configure({\n enabled: process.env.NODE_ENV !== 'production',\n }),\n ],\n value: value,\n },\n []\n );\n\n return a;\n};\n\nexport default function PlaygroundDemo({\n id,\n className,\n scrollSelector,\n}: {\n id?: ValueId;\n className?: string;\n scrollSelector?: string;\n}) {\n const containerRef = useRef(null);\n const enabled = settingsStore.use.checkedComponents();\n\n const editor = usePlaygroundEditor(id, scrollSelector);\n\n return (\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n );\n}\n\nconst DemoIdContext = React.createContext(undefined);\n\nexport function DemoId({\n id,\n children,\n}: {\n children: React.ReactNode;\n id?: string;\n}) {\n return {children};\n}\n\nexport function useDemoId() {\n return React.useContext(DemoIdContext);\n}\n", + "content": "'use client';\n\nimport React, { useRef } from 'react';\nimport { DndProvider } from 'react-dnd';\nimport { HTML5Backend } from 'react-dnd-html5-backend';\n\nimport type { ValueId } from '@/config/customizer-plugins';\n\nimport { cn } from '@udecode/cn';\nimport { AlignPlugin } from '@udecode/plate-alignment/react';\nimport { AutoformatPlugin } from '@udecode/plate-autoformat/react';\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n SubscriptPlugin,\n SuperscriptPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { SingleLinePlugin } from '@udecode/plate-break/react';\nimport { CaptionPlugin } from '@udecode/plate-caption/react';\nimport { CodeBlockPlugin } from '@udecode/plate-code-block/react';\nimport { CommentsPlugin } from '@udecode/plate-comments/react';\nimport {\n ParagraphPlugin,\n Plate,\n usePlateEditor,\n} from '@udecode/plate-common/react';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { DndPlugin } from '@udecode/plate-dnd';\nimport { DocxPlugin } from '@udecode/plate-docx';\nimport { EmojiPlugin } from '@udecode/plate-emoji/react';\nimport { ExcalidrawPlugin } from '@udecode/plate-excalidraw/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n FontSizePlugin,\n} from '@udecode/plate-font/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { HeadingPlugin, TocPlugin } from '@udecode/plate-heading/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';\nimport { IndentPlugin } from '@udecode/plate-indent/react';\nimport { IndentListPlugin } from '@udecode/plate-indent-list/react';\nimport { JuicePlugin } from '@udecode/plate-juice';\nimport { KbdPlugin } from '@udecode/plate-kbd/react';\nimport { ColumnPlugin } from '@udecode/plate-layout/react';\nimport { LineHeightPlugin } from '@udecode/plate-line-height/react';\nimport { LinkPlugin } from '@udecode/plate-link/react';\nimport { ListPlugin, TodoListPlugin } from '@udecode/plate-list/react';\nimport { MarkdownPlugin } from '@udecode/plate-markdown';\nimport { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react';\nimport { MentionPlugin } from '@udecode/plate-mention/react';\nimport { NodeIdPlugin } from '@udecode/plate-node-id';\nimport { NormalizeTypesPlugin } from '@udecode/plate-normalizers';\nimport { PlaywrightPlugin } from '@udecode/plate-playwright';\nimport { DeletePlugin, SelectOnBackspacePlugin } from '@udecode/plate-select';\nimport {\n BlockMenuPlugin,\n BlockSelectionPlugin,\n} from '@udecode/plate-selection/react';\nimport { SlashPlugin } from '@udecode/plate-slash-command/react';\nimport { TablePlugin } from '@udecode/plate-table/react';\nimport { TogglePlugin } from '@udecode/plate-toggle/react';\nimport { TrailingBlockPlugin } from '@udecode/plate-trailing-block';\nimport Prism from 'prismjs';\n\nimport { CheckPlugin } from '@/components/context/check-plugin';\nimport { settingsStore } from '@/components/context/settings-store';\nimport { PlaygroundFixedToolbarButtons } from '@/components/plate-ui/playground-fixed-toolbar-buttons';\nimport { PlaygroundFloatingToolbar } from '@/components/plate-ui/playground-floating-toolbar';\nimport { PlaygroundFloatingToolbarButtons } from '@/components/plate-ui/playground-floating-toolbar-buttons';\nimport { aiPlugins } from '@/lib/plate/demo/plugins/ai-plugins';\nimport { getAutoformatOptions } from '@/lib/plate/demo/plugins/autoformatOptions';\nimport { copilotPlugins } from '@/lib/plate/demo/plugins/copilot-plugins';\nimport { createPlateUI } from '@/plate/create-plate-ui';\nimport { editableProps } from '@/plate/demo/editableProps';\nimport { isEnabled } from '@/plate/demo/is-enabled';\nimport { DragOverCursorPlugin } from '@/plate/demo/plugins/DragOverCursorPlugin';\nimport { exitBreakPlugin } from '@/plate/demo/plugins/exitBreakPlugin';\nimport { resetBlockTypePlugin } from '@/plate/demo/plugins/resetBlockTypePlugin';\nimport { softBreakPlugin } from '@/plate/demo/plugins/softBreakPlugin';\nimport { tabbablePlugin } from '@/plate/demo/plugins/tabbablePlugin';\nimport { commentsData, usersData } from '@/plate/demo/values/commentsValue';\nimport { usePlaygroundValue } from '@/plate/demo/values/usePlaygroundValue';\nimport { BlockContextMenu } from '@/components/plate-ui/block-context-menu';\nimport { CommentsPopover } from '@/components/plate-ui/comments-popover';\nimport {\n CursorOverlay,\n SelectionOverlayPlugin,\n} from '@/components/plate-ui/cursor-overlay';\nimport { Editor } from '@/components/plate-ui/editor';\nimport { FixedToolbar } from '@/components/plate-ui/fixed-toolbar';\nimport { ImagePreview } from '@/components/plate-ui/image-preview';\nimport {\n FireLiComponent,\n FireMarker,\n} from '@/components/plate-ui/indent-fire-marker';\nimport {\n TodoLi,\n TodoMarker,\n} from '@/components/plate-ui/indent-todo-marker';\nimport { LinkFloatingToolbar } from '@/components/plate-ui/link-floating-toolbar';\n\nimport { usePlaygroundEnabled } from './usePlaygroundEnabled';\n\nexport const usePlaygroundEditor = (id: any = '', scrollSelector?: string) => {\n const enabledPlugins = settingsStore.use.checkedPlugins();\n const overridePlugins = usePlaygroundEnabled(id);\n const autoformatOptions = getAutoformatOptions(id, enabledPlugins);\n\n const value = usePlaygroundValue(id);\n const key = settingsStore.use.version();\n\n const editorId = id || 'playground-' + key;\n\n return usePlateEditor(\n {\n id: editorId,\n override: {\n components: createPlateUI({\n draggable: isEnabled('dnd', id),\n placeholder: isEnabled('placeholder', id),\n }),\n plugins: overridePlugins,\n },\n plugins: [\n ...aiPlugins,\n ...(id === 'copilot' ? copilotPlugins : []),\n // Nodes\n HeadingPlugin,\n TocPlugin.configure({\n options: {\n scrollContainerSelector: `#${scrollSelector}`,\n topOffset: 80,\n },\n }),\n BlockquotePlugin,\n CodeBlockPlugin.configure({\n options: {\n prism: Prism,\n },\n }),\n HorizontalRulePlugin,\n LinkPlugin.extend({\n render: { afterEditable: () => },\n }),\n ...(id === 'list' ? [ListPlugin] : []),\n ImagePlugin.extend({\n render: { afterEditable: ImagePreview },\n }),\n MediaEmbedPlugin,\n CaptionPlugin.configure({\n options: {\n plugins: [ImagePlugin, MediaEmbedPlugin],\n },\n }),\n DatePlugin,\n MentionPlugin.configure({\n options: {\n triggerPreviousCharPattern: /^$|^[\\s\"']$/,\n },\n }),\n SlashPlugin,\n TablePlugin.configure({\n options: {\n enableMerging: id === 'tableMerge',\n },\n }),\n SelectionOverlayPlugin,\n\n TodoListPlugin,\n TogglePlugin,\n ExcalidrawPlugin,\n // Marks\n BoldPlugin,\n ItalicPlugin,\n UnderlinePlugin,\n StrikethroughPlugin,\n CodePlugin,\n SubscriptPlugin,\n SuperscriptPlugin,\n FontColorPlugin,\n FontBackgroundColorPlugin,\n FontSizePlugin,\n HighlightPlugin,\n KbdPlugin,\n\n // Block Style\n AlignPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n MediaEmbedPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n ImagePlugin.key,\n HEADING_KEYS.h6,\n ],\n },\n }),\n IndentPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n BlockquotePlugin.key,\n CodeBlockPlugin.key,\n TogglePlugin.key,\n ],\n },\n }),\n IndentListPlugin.extend({\n inject: {\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n BlockquotePlugin.key,\n CodeBlockPlugin.key,\n TogglePlugin.key,\n ],\n },\n options: {\n listStyleTypes: {\n fire: {\n liComponent: FireLiComponent,\n markerComponent: FireMarker,\n type: 'fire',\n },\n todo: {\n liComponent: TodoLi,\n markerComponent: TodoMarker,\n type: 'todo',\n },\n },\n },\n }),\n LineHeightPlugin.extend({\n inject: {\n nodeProps: {\n defaultNodeValue: 1.5,\n validNodeValues: [1, 1.2, 1.5, 2, 3],\n },\n targetPlugins: [\n ParagraphPlugin.key,\n HEADING_KEYS.h1,\n HEADING_KEYS.h2,\n HEADING_KEYS.h3,\n HEADING_KEYS.h4,\n HEADING_KEYS.h5,\n HEADING_KEYS.h6,\n ],\n },\n }),\n\n // Functionality\n AutoformatPlugin.configure({ options: autoformatOptions }),\n BlockSelectionPlugin.configure({\n options: {\n areaOptions: {\n behaviour: {\n scrolling: {\n speedDivider: 1.5,\n },\n startThreshold: 10,\n },\n boundaries: `#${scrollSelector}`,\n container: `#${scrollSelector}`,\n selectables: [`#${scrollSelector} .slate-selectable`],\n selectionAreaClass: 'slate-selection-area',\n },\n enableContextMenu: true,\n },\n }),\n BlockMenuPlugin.configure({\n render: { aboveEditable: BlockContextMenu },\n }),\n DndPlugin.configure({ options: { enableScroller: true } }),\n EmojiPlugin,\n exitBreakPlugin,\n NodeIdPlugin,\n NormalizeTypesPlugin.configure({\n options: {\n rules: [{ path: [0], strictType: HEADING_KEYS.h1 }],\n },\n }),\n resetBlockTypePlugin,\n SelectOnBackspacePlugin.configure({\n options: {\n query: {\n allow: [ImagePlugin.key, HorizontalRulePlugin.key],\n },\n },\n }),\n DeletePlugin,\n SingleLinePlugin,\n softBreakPlugin,\n tabbablePlugin,\n TrailingBlockPlugin.configure({\n options: { type: ParagraphPlugin.key },\n }),\n DragOverCursorPlugin,\n\n // Collaboration\n CommentsPlugin.configure({\n options: {\n comments: commentsData,\n myUserId: '1',\n users: usersData,\n },\n }),\n\n // Deserialization\n DocxPlugin,\n MarkdownPlugin.configure({ options: { indentList: true } }),\n JuicePlugin,\n ColumnPlugin,\n\n // Testing\n PlaywrightPlugin.configure({\n enabled: process.env.NODE_ENV !== 'production',\n }),\n ],\n value: value,\n },\n []\n );\n};\n\nexport default function PlaygroundDemo({\n id,\n className,\n scrollSelector,\n}: {\n id?: ValueId;\n className?: string;\n scrollSelector?: string;\n}) {\n const containerRef = useRef(null);\n const enabled = settingsStore.use.checkedComponents();\n\n const editor = usePlaygroundEditor(\n id,\n scrollSelector ?? `blockSelection-${id}`\n );\n\n return (\n \n \n \n \n \n \n \n \n \n \n\n
\n \n \n\n \n \n \n \n \n \n \n\n \n \n \n
\n\n \n \n \n \n
\n
\n
\n );\n}\n\nconst DemoIdContext = React.createContext(undefined);\n\nexport function DemoId({\n id,\n children,\n}: {\n children: React.ReactNode;\n id?: string;\n}) {\n return {children};\n}\n\nexport function useDemoId() {\n return React.useContext(DemoIdContext);\n}\n", "path": "example/playground-demo.tsx", "target": "components/playground-demo.tsx", "type": "registry:example" diff --git a/apps/www/public/r/styles/default/potion-iframe-demo.json b/apps/www/public/r/styles/default/potion-iframe-demo.json new file mode 100644 index 0000000000..d64f83985e --- /dev/null +++ b/apps/www/public/r/styles/default/potion-iframe-demo.json @@ -0,0 +1,12 @@ +{ + "files": [ + { + "content": "export default function PotionIframeDemo() {\n return (\n \n );\n}\n", + "path": "example/potion-iframe-demo.tsx", + "target": "components/potion-iframe-demo.tsx", + "type": "registry:example" + } + ], + "name": "potion-iframe-demo", + "type": "registry:example" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/pro-iframe-demo.json b/apps/www/public/r/styles/default/pro-iframe-demo.json new file mode 100644 index 0000000000..df22f1e79c --- /dev/null +++ b/apps/www/public/r/styles/default/pro-iframe-demo.json @@ -0,0 +1,12 @@ +{ + "files": [ + { + "content": "import { siteConfig } from '@/config/site';\nimport { cn } from '@/lib/utils';\n\nexport default function ProIframeDemo({\n component,\n}: {\n component: string;\n height: string;\n}) {\n return (\n \n );\n}\n", + "path": "example/pro-iframe-demo.tsx", + "target": "components/pro-iframe-demo.tsx", + "type": "registry:example" + } + ], + "name": "pro-iframe-demo", + "type": "registry:example" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/slash-input-element.json b/apps/www/public/r/styles/default/slash-input-element.json index d3b8e0138c..cf51b3239d 100644 --- a/apps/www/public/r/styles/default/slash-input-element.json +++ b/apps/www/public/r/styles/default/slash-input-element.json @@ -5,7 +5,7 @@ ], "files": [ { - "content": "import React, { type ComponentType, type SVGProps } from 'react';\n\nimport type { PlateEditor } from '@udecode/plate-common/react';\n\nimport { withRef } from '@udecode/cn';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { ListStyleType, toggleIndentList } from '@udecode/plate-indent-list';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n InlineCombobox,\n InlineComboboxContent,\n InlineComboboxEmpty,\n InlineComboboxInput,\n InlineComboboxItem,\n} from './inline-combobox';\nimport { PlateElement } from './plate-element';\n\ninterface SlashCommandRule {\n icon: ComponentType>;\n onSelect: (editor: PlateEditor) => void;\n value: string;\n keywords?: string[];\n}\n\nconst rules: SlashCommandRule[] = [\n {\n icon: Icons.h1,\n value: 'Heading 1',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h1 });\n },\n },\n {\n icon: Icons.h2,\n value: 'Heading 2',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h2 });\n },\n },\n {\n icon: Icons.h3,\n value: 'Heading 3',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h3 });\n },\n },\n {\n icon: Icons.ul,\n keywords: ['ul', 'unordered list'],\n value: 'Bulleted list',\n onSelect: (editor) => {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n });\n },\n },\n {\n icon: Icons.ol,\n keywords: ['ol', 'ordered list'],\n value: 'Numbered list',\n onSelect: (editor) => {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n });\n },\n },\n {\n icon: Icons.add,\n keywords: ['inline', 'date'],\n value: 'Date',\n onSelect: (editor) => {\n editor.getTransforms(DatePlugin).insert.date();\n },\n },\n];\n\nexport const SlashInputElement = withRef(\n ({ className, ...props }, ref) => {\n const { children, editor, element } = props;\n\n return (\n \n \n \n\n \n \n No matching commands found\n \n\n {rules.map(({ icon: Icon, keywords, value, onSelect }) => (\n onSelect(editor)}\n keywords={keywords}\n >\n \n {value}\n \n ))}\n \n \n\n {children}\n \n );\n }\n);\n", + "content": "import React, { type ComponentType, type SVGProps } from 'react';\n\nimport type { PlateEditor } from '@udecode/plate-common/react';\n\nimport { withRef } from '@udecode/cn';\nimport { AIChatPlugin } from '@udecode/plate-ai/react';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { ListStyleType, toggleIndentList } from '@udecode/plate-indent-list';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n InlineCombobox,\n InlineComboboxContent,\n InlineComboboxEmpty,\n InlineComboboxInput,\n InlineComboboxItem,\n} from './inline-combobox';\nimport { PlateElement } from './plate-element';\n\ninterface SlashCommandRule {\n icon: ComponentType>;\n onSelect: (editor: PlateEditor) => void;\n value: string;\n className?: string;\n focusEditor?: boolean;\n keywords?: string[];\n}\n\nconst rules: SlashCommandRule[] = [\n {\n focusEditor: false,\n icon: Icons.ai,\n value: 'AI',\n onSelect: (editor) => {\n editor.getApi(AIChatPlugin).aiChat.show();\n },\n },\n {\n icon: Icons.h1,\n value: 'Heading 1',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h1 });\n },\n },\n {\n icon: Icons.h2,\n value: 'Heading 2',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h2 });\n },\n },\n {\n icon: Icons.h3,\n value: 'Heading 3',\n onSelect: (editor) => {\n editor.tf.toggle.block({ type: HEADING_KEYS.h3 });\n },\n },\n {\n icon: Icons.ul,\n keywords: ['ul', 'unordered list'],\n value: 'Bulleted list',\n onSelect: (editor) => {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n });\n },\n },\n {\n icon: Icons.ol,\n keywords: ['ol', 'ordered list'],\n value: 'Numbered list',\n onSelect: (editor) => {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n });\n },\n },\n {\n icon: Icons.add,\n keywords: ['inline', 'date'],\n value: 'Date',\n onSelect: (editor) => {\n editor.getTransforms(DatePlugin).insert.date();\n },\n },\n];\n\nexport const SlashInputElement = withRef(\n ({ className, ...props }, ref) => {\n const { children, editor, element } = props;\n\n return (\n \n \n \n\n \n \n No matching commands found\n \n\n {rules.map(\n ({ focusEditor, icon: Icon, keywords, value, onSelect }) => (\n onSelect(editor)}\n focusEditor={focusEditor}\n keywords={keywords}\n >\n \n {value}\n \n )\n )}\n \n \n\n {children}\n \n );\n }\n);\n", "path": "plate-ui/slash-input-element.tsx", "target": "components/plate-ui/slash-input-element.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/toc-element.json b/apps/www/public/r/styles/default/toc-element.json new file mode 100644 index 0000000000..c9ac6eafdb --- /dev/null +++ b/apps/www/public/r/styles/default/toc-element.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "@udecode/plate-heading" + ], + "files": [ + { + "content": "import { cn, withRef } from '@udecode/cn';\nimport {\n useTocElement,\n useTocElementState,\n} from '@udecode/plate-heading/react';\nimport { cva } from 'class-variance-authority';\n\nimport { Button } from './button';\nimport { PlateElement } from './plate-element';\n\nconst headingItemVariants = cva(\n 'block h-auto w-full cursor-pointer truncate rounded-none px-0.5 py-1.5 text-left font-medium text-muted-foreground underline decoration-[0.5px] underline-offset-4 hover:bg-accent hover:text-muted-foreground',\n {\n variants: {\n depth: {\n 1: 'pl-0.5',\n 2: 'pl-[26px]',\n 3: 'pl-[50px]',\n },\n },\n }\n);\n\nexport const TocElement = withRef(\n ({ children, className, ...props }, ref) => {\n const state = useTocElementState();\n\n const { props: btnProps } = useTocElement(state);\n\n const { headingList } = state;\n\n return (\n \n \n {children}\n \n );\n }\n);\n", + "path": "plate-ui/toc-element.tsx", + "target": "components/plate-ui/toc-element.tsx", + "type": "registry:ui" + } + ], + "name": "toc-element", + "registryDependencies": [ + "" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/toolbar.json b/apps/www/public/r/styles/default/toolbar.json index 6314b41734..d371a9d259 100644 --- a/apps/www/public/r/styles/default/toolbar.json +++ b/apps/www/public/r/styles/default/toolbar.json @@ -4,7 +4,7 @@ ], "files": [ { - "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as ToolbarPrimitive from '@radix-ui/react-toolbar';\nimport { cn, withCn, withRef, withVariants } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nimport { Icons } from '@/components/icons';\n\nimport { Separator } from './separator';\nimport { withTooltip } from './tooltip';\n\nexport const Toolbar = withCn(\n ToolbarPrimitive.Root,\n 'relative flex select-none items-center gap-1 bg-background'\n);\n\nexport const ToolbarToggleGroup = withCn(\n ToolbarPrimitive.ToolbarToggleGroup,\n 'flex items-center'\n);\n\nexport const ToolbarLink = withCn(\n ToolbarPrimitive.Link,\n 'font-medium underline underline-offset-4'\n);\n\nexport const ToolbarSeparator = withCn(\n ToolbarPrimitive.Separator,\n 'my-1 w-px shrink-0 bg-border'\n);\n\nconst toolbarButtonVariants = cva(\n cn(\n 'inline-flex items-center justify-center rounded-md text-sm font-medium text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n '[&_svg:not([data-icon])]:size-5'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 px-3',\n lg: 'h-11 px-5',\n sm: 'h-9 px-2',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst ToolbarButton = withTooltip(\n // eslint-disable-next-line react/display-name\n React.forwardRef<\n React.ElementRef,\n {\n isDropdown?: boolean;\n pressed?: boolean;\n } & Omit<\n React.ComponentPropsWithoutRef,\n 'asChild' | 'value'\n > &\n VariantProps\n >(\n (\n { children, className, isDropdown, pressed, size, variant, ...props },\n ref\n ) => {\n return typeof pressed === 'boolean' ? (\n \n \n {isDropdown ? (\n <>\n
{children}
\n
\n \n
\n \n ) : (\n children\n )}\n \n \n ) : (\n \n {children}\n \n );\n }\n )\n);\nToolbarButton.displayName = 'ToolbarButton';\n\nexport { ToolbarButton };\n\nexport const ToolbarToggleItem = withVariants(\n ToolbarPrimitive.ToggleItem,\n toolbarButtonVariants,\n ['variant', 'size']\n);\n\nexport const ToolbarGroup = withRef<\n 'div',\n {\n noSeparator?: boolean;\n }\n>(({ children, className, noSeparator }, ref) => {\n const childArr = React.Children.map(children, (c) => c);\n\n if (!childArr || childArr.length === 0) return null;\n\n return (\n
\n {!noSeparator && (\n
\n \n
\n )}\n\n
{children}
\n
\n );\n});\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as ToolbarPrimitive from '@radix-ui/react-toolbar';\nimport { cn, withCn, withRef, withVariants } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nimport { Icons } from '@/components/icons';\n\nimport { Separator } from './separator';\nimport { withTooltip } from './tooltip';\n\nexport const Toolbar = withCn(\n ToolbarPrimitive.Root,\n 'relative flex select-none items-center'\n);\n\nexport const ToolbarToggleGroup = withCn(\n ToolbarPrimitive.ToolbarToggleGroup,\n 'flex items-center'\n);\n\nexport const ToolbarLink = withCn(\n ToolbarPrimitive.Link,\n 'font-medium underline underline-offset-4'\n);\n\nexport const ToolbarSeparator = withCn(\n ToolbarPrimitive.Separator,\n 'mx-2 my-1 w-px shrink-0 bg-border'\n);\n\nconst toolbarButtonVariants = cva(\n cn(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n '[&_svg:not([data-icon])]:size-4'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 px-3',\n lg: 'h-11 px-5',\n sm: 'h-7 px-2',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst ToolbarButton = withTooltip(\n // eslint-disable-next-line react/display-name\n React.forwardRef<\n React.ElementRef,\n {\n isDropdown?: boolean;\n pressed?: boolean;\n } & Omit<\n React.ComponentPropsWithoutRef,\n 'asChild' | 'value'\n > &\n VariantProps\n >(\n (\n { children, className, isDropdown, pressed, size, variant, ...props },\n ref\n ) => {\n return typeof pressed === 'boolean' ? (\n \n \n {isDropdown ? (\n <>\n
\n {children}\n
\n
\n \n
\n \n ) : (\n children\n )}\n \n \n ) : (\n \n {children}\n \n );\n }\n )\n);\nToolbarButton.displayName = 'ToolbarButton';\n\nexport { ToolbarButton };\n\nexport const ToolbarToggleItem = withVariants(\n ToolbarPrimitive.ToggleItem,\n toolbarButtonVariants,\n ['variant', 'size']\n);\n\nexport const ToolbarGroup = withRef<'div'>(({ children, className }, ref) => {\n const childArr = React.Children.map(children, (c) => c);\n\n if (!childArr || childArr.length === 0) return null;\n\n return (\n \n
{children}
\n\n \n \n );\n});\n", "path": "plate-ui/toolbar.tsx", "target": "components/plate-ui/toolbar.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/turn-into-dropdown-menu.json b/apps/www/public/r/styles/default/turn-into-dropdown-menu.json index 502b986c06..ee44d3b138 100644 --- a/apps/www/public/r/styles/default/turn-into-dropdown-menu.json +++ b/apps/www/public/r/styles/default/turn-into-dropdown-menu.json @@ -5,7 +5,7 @@ ], "files": [ { - "content": "import React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { getNodeEntries, isBlock } from '@udecode/plate-common';\nimport {\n ParagraphPlugin,\n focusEditor,\n useEditorRef,\n useEditorSelector,\n} from '@udecode/plate-common/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { ListStyleType, toggleIndentList } from '@udecode/plate-indent-list';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\nconst items = [\n {\n description: 'Paragraph',\n icon: Icons.paragraph,\n label: 'Paragraph',\n value: ParagraphPlugin.key,\n },\n {\n description: 'Heading 1',\n icon: Icons.h1,\n label: 'Heading 1',\n value: HEADING_KEYS.h1,\n },\n {\n description: 'Heading 2',\n icon: Icons.h2,\n label: 'Heading 2',\n value: HEADING_KEYS.h2,\n },\n {\n description: 'Heading 3',\n icon: Icons.h3,\n label: 'Heading 3',\n value: HEADING_KEYS.h3,\n },\n {\n description: 'Quote (⌘+⇧+.)',\n icon: Icons.blockquote,\n label: 'Quote',\n value: BlockquotePlugin.key,\n },\n {\n description: 'Bulleted list',\n icon: Icons.ul,\n label: 'Bulleted list',\n value: ListStyleType.Disc,\n },\n {\n description: 'Numbered list',\n icon: Icons.ol,\n label: 'Numbered list',\n value: ListStyleType.Decimal,\n },\n];\n\nconst defaultItem = items.find((item) => item.value === ParagraphPlugin.key)!;\n\nexport function TurnIntoDropdownMenu(props: DropdownMenuProps) {\n const value: string = useEditorSelector((editor) => {\n let initialNodeType: string = ParagraphPlugin.key;\n let allNodesMatchInitialNodeType = false;\n const codeBlockEntries = getNodeEntries(editor, {\n match: (n) => isBlock(editor, n),\n mode: 'highest',\n });\n const nodes = Array.from(codeBlockEntries);\n\n if (nodes.length > 0) {\n initialNodeType = nodes[0][0].type as string;\n allNodesMatchInitialNodeType = nodes.every(([node]) => {\n const type: string = (node?.type as string) || ParagraphPlugin.key;\n\n return type === initialNodeType;\n });\n }\n\n return allNodesMatchInitialNodeType ? initialNodeType : ParagraphPlugin.key;\n }, []);\n\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const selectedItem =\n // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n items.find((item) => item.value === value) ?? defaultItem;\n const { icon: SelectedItemIcon, label: selectedItemLabel } = selectedItem;\n\n return (\n \n \n \n \n {selectedItemLabel}\n \n \n\n \n Turn into\n\n {\n if (type === ListStyleType.Disc || type === ListStyleType.Decimal) {\n toggleIndentList(editor, {\n listStyleType: type,\n });\n }\n\n editor.tf.toggle.block({ type });\n\n focusEditor(editor);\n }}\n >\n {items.map(({ icon: Icon, label, value: itemValue }) => (\n \n \n {label}\n \n ))}\n \n \n \n );\n}\n", + "content": "import React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { getNodeEntries, isBlock } from '@udecode/plate-common';\nimport {\n ParagraphPlugin,\n focusEditor,\n useEditorRef,\n useEditorSelector,\n} from '@udecode/plate-common/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { ListStyleType, toggleIndentList } from '@udecode/plate-indent-list';\n\nimport { Icons } from '@/components/icons';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\nconst items = [\n {\n description: 'Paragraph',\n icon: Icons.paragraph,\n label: 'Paragraph',\n value: ParagraphPlugin.key,\n },\n {\n description: 'Heading 1',\n icon: Icons.h1,\n label: 'Heading 1',\n value: HEADING_KEYS.h1,\n },\n {\n description: 'Heading 2',\n icon: Icons.h2,\n label: 'Heading 2',\n value: HEADING_KEYS.h2,\n },\n {\n description: 'Heading 3',\n icon: Icons.h3,\n label: 'Heading 3',\n value: HEADING_KEYS.h3,\n },\n {\n description: 'Quote (⌘+⇧+.)',\n icon: Icons.blockquote,\n label: 'Quote',\n value: BlockquotePlugin.key,\n },\n {\n description: 'Bulleted list',\n icon: Icons.ul,\n label: 'Bulleted list',\n value: ListStyleType.Disc,\n },\n {\n description: 'Numbered list',\n icon: Icons.ol,\n label: 'Numbered list',\n value: ListStyleType.Decimal,\n },\n];\n\nconst defaultItem = items.find((item) => item.value === ParagraphPlugin.key)!;\n\nexport function TurnIntoDropdownMenu(props: DropdownMenuProps) {\n const value: string = useEditorSelector((editor) => {\n let initialNodeType: string = ParagraphPlugin.key;\n let allNodesMatchInitialNodeType = false;\n const codeBlockEntries = getNodeEntries(editor, {\n match: (n) => isBlock(editor, n),\n mode: 'highest',\n });\n const nodes = Array.from(codeBlockEntries);\n\n if (nodes.length > 0) {\n initialNodeType = nodes[0][0].type as string;\n allNodesMatchInitialNodeType = nodes.every(([node]) => {\n const type: string = (node?.type as string) || ParagraphPlugin.key;\n\n return type === initialNodeType;\n });\n }\n\n return allNodesMatchInitialNodeType ? initialNodeType : ParagraphPlugin.key;\n }, []);\n\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const selectedItem =\n // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n items.find((item) => item.value === value) ?? defaultItem;\n const { icon: SelectedItemIcon, label: selectedItemLabel } = selectedItem;\n\n return (\n \n \n \n \n {selectedItemLabel}\n \n \n\n \n Turn into\n\n {\n if (type === ListStyleType.Disc || type === ListStyleType.Decimal) {\n toggleIndentList(editor, {\n listStyleType: type,\n });\n }\n\n editor.tf.toggle.block({ type });\n\n focusEditor(editor);\n }}\n >\n {items.map(({ icon: Icon, label, value: itemValue }) => (\n \n \n {label}\n \n ))}\n \n \n \n );\n}\n", "path": "plate-ui/turn-into-dropdown-menu.tsx", "target": "components/plate-ui/turn-into-dropdown-menu.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/version-history-demo.json b/apps/www/public/r/styles/default/version-history-demo.json index bbc33b06aa..d191c95a8e 100644 --- a/apps/www/public/r/styles/default/version-history-demo.json +++ b/apps/www/public/r/styles/default/version-history-demo.json @@ -1,7 +1,7 @@ { "files": [ { - "content": "import React from 'react';\n\nimport { cn } from '@udecode/cn';\nimport { BoldPlugin, ItalicPlugin } from '@udecode/plate-basic-marks/react';\nimport { SoftBreakPlugin } from '@udecode/plate-break/react';\nimport { type Value, createSlatePlugin, isInline } from '@udecode/plate-common';\nimport {\n ParagraphPlugin,\n createPlatePlugin,\n toPlatePlugin,\n} from '@udecode/plate-common/react';\nimport {\n type PlateElementProps,\n type PlateLeafProps,\n type PlateProps,\n Plate,\n PlateContent,\n PlateElement,\n PlateLeaf,\n createPlateEditor,\n usePlateEditor,\n} from '@udecode/plate-common/react';\nimport {\n type DiffOperation,\n type DiffUpdate,\n computeDiff,\n withGetFragmentExcludeDiff,\n} from '@udecode/plate-diff';\nimport { cloneDeep } from 'lodash';\nimport { useSelected } from 'slate-react';\n\nimport { PlateUI } from '@/lib/plate/demo/plate-ui';\nimport { Button } from '@/components/plate-ui/button';\n\nconst InlinePlugin = createPlatePlugin({\n key: 'inline',\n node: { isElement: true, isInline: true },\n});\n\nconst InlineVoidPlugin = createPlatePlugin({\n key: 'inline-void',\n node: { isElement: true, isInline: true, isVoid: true },\n});\n\nconst diffOperationColors: Record = {\n delete: 'bg-red-200',\n insert: 'bg-green-200',\n update: 'bg-blue-200',\n};\n\nconst describeUpdate = ({ newProperties, properties }: DiffUpdate) => {\n const addedProps: string[] = [];\n const removedProps: string[] = [];\n const updatedProps: string[] = [];\n\n Object.keys(newProperties).forEach((key) => {\n const oldValue = properties[key];\n const newValue = newProperties[key];\n\n if (oldValue === undefined) {\n addedProps.push(key);\n\n return;\n }\n if (newValue === undefined) {\n removedProps.push(key);\n\n return;\n }\n\n updatedProps.push(key);\n });\n\n const descriptionParts = [];\n\n if (addedProps.length > 0) {\n descriptionParts.push(`Added ${addedProps.join(', ')}`);\n }\n if (removedProps.length > 0) {\n descriptionParts.push(`Removed ${removedProps.join(', ')}`);\n }\n if (updatedProps.length > 0) {\n updatedProps.forEach((key) => {\n descriptionParts.push(\n `Updated ${key} from ${properties[key]} to ${newProperties[key]}`\n );\n });\n }\n\n return descriptionParts.join('\\n');\n};\n\nconst InlineElement = ({ children, ...props }: PlateElementProps) => {\n return (\n \n {children}\n \n );\n};\n\nconst InlineVoidElement = ({ children, ...props }: PlateElementProps) => {\n const selected = useSelected();\n\n return (\n \n \n Inline void\n \n {children}\n \n );\n};\n\nconst DiffPlugin = toPlatePlugin(\n createSlatePlugin({\n key: 'diff',\n extendEditor: withGetFragmentExcludeDiff,\n node: { isLeaf: true },\n }),\n {\n render: {\n aboveNodes:\n () =>\n ({ children, editor, element }) => {\n if (!element.diff) return children;\n\n const diffOperation = element.diffOperation as DiffOperation;\n\n const label = (\n {\n delete: 'deletion',\n insert: 'insertion',\n update: 'update',\n } as any\n )[diffOperation.type];\n\n const Component = isInline(editor, element) ? 'span' : 'div';\n\n return (\n \n {children}\n \n );\n },\n node: DiffLeaf,\n },\n }\n);\n\nfunction DiffLeaf({ children, ...props }: PlateLeafProps) {\n const diffOperation = props.leaf.diffOperation as DiffOperation;\n\n const Component = (\n {\n delete: 'del',\n insert: 'ins',\n update: 'span',\n } as any\n )[diffOperation.type];\n\n return (\n \n \n {children}\n \n \n );\n}\n\nconst initialValue: Value = [\n {\n children: [{ text: 'This is a version history demo.' }],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'Try editing the ' },\n { bold: true, text: 'text and see what' },\n { text: ' happens.' },\n ],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'This is an ' },\n { children: [{ text: '' }], type: InlineVoidPlugin.key },\n { text: '. Try removing it.' },\n ],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'This is an ' },\n { children: [{ text: 'editable inline' }], type: InlinePlugin.key },\n { text: '. Try editing it.' },\n ],\n type: ParagraphPlugin.key,\n },\n];\n\nconst plugins = [\n InlinePlugin.withComponent(InlineElement),\n InlineVoidPlugin.withComponent(InlineVoidElement),\n BoldPlugin,\n ItalicPlugin,\n DiffPlugin,\n SoftBreakPlugin,\n];\n\nfunction VersionHistoryPlate(props: Omit) {\n return (\n \n \n \n );\n}\n\ninterface DiffProps {\n current: Value;\n previous: Value;\n}\n\nfunction Diff({ current, previous }: DiffProps) {\n const diffValue = React.useMemo(() => {\n const editor = createPlateEditor({\n plugins,\n });\n\n return computeDiff(previous, cloneDeep(current), {\n isInline: editor.isInline,\n lineBreakChar: '¶',\n }) as Value;\n }, [previous, current]);\n\n const editor = usePlateEditor(\n {\n override: { components: PlateUI },\n plugins,\n value: diffValue,\n },\n [diffValue]\n );\n\n return (\n <>\n \n\n
{JSON.stringify(diffValue, null, 2)}
\n \n );\n}\n\nexport default function VersionHistoryDemo() {\n const [revisions, setRevisions] = React.useState([initialValue]);\n const [selectedRevisionIndex, setSelectedRevisionIndex] =\n React.useState(0);\n const [value, setValue] = React.useState(initialValue);\n\n const selectedRevisionValue = React.useMemo(\n () => revisions[selectedRevisionIndex],\n [revisions, selectedRevisionIndex]\n );\n\n const saveRevision = () => {\n setRevisions([...revisions, value]);\n };\n\n const editor = usePlateEditor({\n override: { components: PlateUI },\n plugins,\n value: initialValue,\n });\n\n const editorRevision = usePlateEditor(\n {\n override: { components: PlateUI },\n plugins,\n value: selectedRevisionValue,\n },\n [selectedRevisionValue]\n );\n\n return (\n
\n \n\n setValue(value)}\n editor={editor}\n />\n\n \n\n
\n
\n

Revision {selectedRevisionIndex + 1}

\n \n
\n\n
\n

Diff

\n \n
\n
\n
\n );\n}\n", + "content": "import React from 'react';\n\nimport { cn } from '@udecode/cn';\nimport { BoldPlugin, ItalicPlugin } from '@udecode/plate-basic-marks/react';\nimport { SoftBreakPlugin } from '@udecode/plate-break/react';\nimport { type Value, createSlatePlugin, isInline } from '@udecode/plate-common';\nimport {\n ParagraphPlugin,\n createPlatePlugin,\n toPlatePlugin,\n} from '@udecode/plate-common/react';\nimport {\n type PlateElementProps,\n type PlateLeafProps,\n type PlateProps,\n Plate,\n PlateContent,\n PlateElement,\n PlateLeaf,\n createPlateEditor,\n usePlateEditor,\n} from '@udecode/plate-common/react';\nimport {\n type DiffOperation,\n type DiffUpdate,\n computeDiff,\n withGetFragmentExcludeDiff,\n} from '@udecode/plate-diff';\nimport { cloneDeep } from 'lodash';\nimport { useSelected } from 'slate-react';\n\nimport { PlateUI } from '@/lib/plate/demo/plate-ui';\nimport { Button } from '@/components/plate-ui/button';\n\nconst InlinePlugin = createPlatePlugin({\n key: 'inline',\n node: { isElement: true, isInline: true },\n});\n\nconst InlineVoidPlugin = createPlatePlugin({\n key: 'inline-void',\n node: { isElement: true, isInline: true, isVoid: true },\n});\n\nconst diffOperationColors: Record = {\n delete: 'bg-red-200',\n insert: 'bg-green-200',\n update: 'bg-blue-200',\n};\n\nconst describeUpdate = ({ newProperties, properties }: DiffUpdate) => {\n const addedProps: string[] = [];\n const removedProps: string[] = [];\n const updatedProps: string[] = [];\n\n Object.keys(newProperties).forEach((key) => {\n const oldValue = properties[key];\n const newValue = newProperties[key];\n\n if (oldValue === undefined) {\n addedProps.push(key);\n\n return;\n }\n if (newValue === undefined) {\n removedProps.push(key);\n\n return;\n }\n\n updatedProps.push(key);\n });\n\n const descriptionParts = [];\n\n if (addedProps.length > 0) {\n descriptionParts.push(`Added ${addedProps.join(', ')}`);\n }\n if (removedProps.length > 0) {\n descriptionParts.push(`Removed ${removedProps.join(', ')}`);\n }\n if (updatedProps.length > 0) {\n updatedProps.forEach((key) => {\n descriptionParts.push(\n `Updated ${key} from ${properties[key]} to ${newProperties[key]}`\n );\n });\n }\n\n return descriptionParts.join('\\n');\n};\n\nconst InlineElement = ({ children, ...props }: PlateElementProps) => {\n return (\n \n {children}\n \n );\n};\n\nconst InlineVoidElement = ({ children, ...props }: PlateElementProps) => {\n const selected = useSelected();\n\n return (\n \n \n Inline void\n \n {children}\n \n );\n};\n\nconst DiffPlugin = toPlatePlugin(\n createSlatePlugin({\n key: 'diff',\n extendEditor: withGetFragmentExcludeDiff,\n node: { isLeaf: true },\n }),\n {\n render: {\n aboveNodes:\n () =>\n ({ children, editor, element }) => {\n if (!element.diff) return children;\n\n const diffOperation = element.diffOperation as DiffOperation;\n\n const label = (\n {\n delete: 'deletion',\n insert: 'insertion',\n update: 'update',\n } as any\n )[diffOperation.type];\n\n const Component = isInline(editor, element) ? 'span' : 'div';\n\n return (\n \n {children}\n \n );\n },\n node: DiffLeaf,\n },\n }\n);\n\nfunction DiffLeaf({ children, ...props }: PlateLeafProps) {\n const diffOperation = props.leaf.diffOperation as DiffOperation;\n\n const Component = (\n {\n delete: 'del',\n insert: 'ins',\n update: 'span',\n } as any\n )[diffOperation.type];\n\n return (\n \n \n {children}\n \n \n );\n}\n\nconst initialValue: Value = [\n {\n children: [{ text: 'This is a version history demo.' }],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'Try editing the ' },\n { bold: true, text: 'text and see what' },\n { text: ' happens.' },\n ],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'This is an ' },\n { children: [{ text: '' }], type: InlineVoidPlugin.key },\n { text: '. Try removing it.' },\n ],\n type: ParagraphPlugin.key,\n },\n {\n children: [\n { text: 'This is an ' },\n { children: [{ text: 'editable inline' }], type: InlinePlugin.key },\n { text: '. Try editing it.' },\n ],\n type: ParagraphPlugin.key,\n },\n];\n\nconst plugins = [\n InlinePlugin.withComponent(InlineElement),\n InlineVoidPlugin.withComponent(InlineVoidElement),\n BoldPlugin,\n ItalicPlugin,\n DiffPlugin,\n SoftBreakPlugin,\n];\n\nfunction VersionHistoryPlate(props: Omit) {\n return (\n \n \n \n );\n}\n\ninterface DiffProps {\n current: Value;\n previous: Value;\n}\n\nfunction Diff({ current, previous }: DiffProps) {\n const diffValue = React.useMemo(() => {\n const editor = createPlateEditor({\n plugins,\n });\n\n return computeDiff(previous, cloneDeep(current), {\n isInline: editor.isInline,\n lineBreakChar: '¶',\n }) as Value;\n }, [previous, current]);\n\n const editor = usePlateEditor(\n {\n override: { components: PlateUI },\n plugins,\n value: diffValue,\n },\n [diffValue]\n );\n\n return (\n <>\n \n\n {/*
{JSON.stringify(diffValue, null, 2)}
*/}\n \n );\n}\n\nexport default function VersionHistoryDemo() {\n const [revisions, setRevisions] = React.useState([initialValue]);\n const [selectedRevisionIndex, setSelectedRevisionIndex] =\n React.useState(0);\n const [value, setValue] = React.useState(initialValue);\n\n const selectedRevisionValue = React.useMemo(\n () => revisions[selectedRevisionIndex],\n [revisions, selectedRevisionIndex]\n );\n\n const saveRevision = () => {\n setRevisions([...revisions, value]);\n };\n\n const editor = usePlateEditor({\n override: { components: PlateUI },\n plugins,\n value: initialValue,\n });\n\n const editorRevision = usePlateEditor(\n {\n override: { components: PlateUI },\n plugins,\n value: selectedRevisionValue,\n },\n [selectedRevisionValue]\n );\n\n return (\n
\n \n\n setValue(value)}\n editor={editor}\n />\n\n \n\n
\n
\n

Revision {selectedRevisionIndex + 1}

\n \n
\n\n
\n

Diff

\n \n
\n
\n
\n );\n}\n", "path": "example/version-history-demo.tsx", "target": "components/version-history-demo.tsx", "type": "registry:example" diff --git a/apps/www/scripts/capture-screenshots.mts b/apps/www/scripts/capture-screenshots.mts index 5728eec8a6..c56e0ec926 100644 --- a/apps/www/scripts/capture-screenshots.mts +++ b/apps/www/scripts/capture-screenshots.mts @@ -13,10 +13,10 @@ try { }, }) - console.log("☀️ Capturing screenshots for light theme") + console.info("☀️ Capturing screenshots for light theme") for (const block of BLOCKS) { const pageUrl = `http://localhost:3000/blocks/default/${block}` - console.log(`- ${block}`) + console.info(`- ${block}`) const page = await browser.newPage() await page.goto(pageUrl, { @@ -36,10 +36,10 @@ try { }) } - console.log("🌙 Capturing screenshots for dark theme") + console.info("🌙 Capturing screenshots for dark theme") for (const block of BLOCKS) { const pageUrl = `http://localhost:3000/blocks/default/${block}` - console.log(`- ${block}`) + console.info(`- ${block}`) const page = await browser.newPage() await page.goto(pageUrl, { @@ -65,7 +65,7 @@ try { } await browser.close() - console.log("✅ Done!") + console.info("✅ Done!") } catch (error) { console.error(error) process.exit(1) diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx index e806c2aaf8..f692bd128f 100644 --- a/apps/www/src/__registry__/index.tsx +++ b/apps/www/src/__registry__/index.tsx @@ -17,6 +17,30 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "ai-menu": { + name: "ai-menu", + description: "", + type: "registry:ui", + registryDependencies: ["button","menu","textarea","editor"], + files: ["registry/default/plate-ui/ai-menu.tsx","registry/default/plate-ui/ai-chat-editor.tsx","registry/default/plate-ui/ai-menu-items.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/ai-menu.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, + "ai-toolbar-button": { + name: "ai-toolbar-button", + description: "", + type: "registry:ui", + registryDependencies: ["toolbar"], + files: ["registry/default/plate-ui/ai-toolbar-button.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/ai-toolbar-button.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "block-selection": { name: "block-selection", description: "", @@ -173,6 +197,54 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "block-context-menu": { + name: "block-context-menu", + description: "", + type: "registry:ui", + registryDependencies: ["calendar","plate-element"], + files: ["registry/default/plate-ui/block-context-menu.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/block-context-menu.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, + "ghost-text": { + name: "ghost-text", + description: "", + type: "registry:ui", + registryDependencies: [""], + files: ["registry/default/plate-ui/ghost-text.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/ghost-text.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, + "ai-leaf": { + name: "ai-leaf", + description: "", + type: "registry:ui", + registryDependencies: [""], + files: ["registry/default/plate-ui/ai-leaf.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/ai-leaf.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, + "toc-element": { + name: "toc-element", + description: "", + type: "registry:ui", + registryDependencies: [""], + files: ["registry/default/plate-ui/toc-element.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/toc-element.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "calendar": { name: "calendar", description: "", @@ -473,13 +545,13 @@ export const Index: Record = { subcategory: "", chunks: [] }, - "indent-todo-marker-component": { - name: "indent-todo-marker-component", + "indent-todo-marker": { + name: "indent-todo-marker", description: "", type: "registry:ui", registryDependencies: ["checkbox"], - files: ["registry/default/plate-ui/indent-todo-marker-component.tsx"], - component: React.lazy(() => import("@/registry/default/plate-ui/indent-todo-marker-component.tsx")), + files: ["registry/default/plate-ui/indent-todo-marker.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/indent-todo-marker.tsx")), source: "", category: "", subcategory: "", @@ -917,6 +989,30 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "pro-iframe-demo": { + name: "pro-iframe-demo", + description: "", + type: "registry:example", + registryDependencies: undefined, + files: ["registry/default/example/pro-iframe-demo.tsx"], + component: React.lazy(() => import("@/registry/default/example/pro-iframe-demo.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, + "potion-iframe-demo": { + name: "potion-iframe-demo", + description: "", + type: "registry:example", + registryDependencies: undefined, + files: ["registry/default/example/potion-iframe-demo.tsx"], + component: React.lazy(() => import("@/registry/default/example/potion-iframe-demo.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "editor-default": { name: "editor-default", description: "", diff --git a/apps/www/src/app/(app)/_components/announcement-button.tsx b/apps/www/src/app/(app)/_components/announcement-button.tsx new file mode 100644 index 0000000000..b0ebde0a0e --- /dev/null +++ b/apps/www/src/app/(app)/_components/announcement-button.tsx @@ -0,0 +1,31 @@ +'use client'; + +import * as React from 'react'; + +import { ArrowRightIcon, PieChart } from 'lucide-react'; + +import { Button } from '@/registry/default/plate-ui/button'; +import { Separator } from '@/registry/default/plate-ui/separator'; + +export function AnnouncementButton() { + return ( + + ); +} diff --git a/apps/www/src/app/_components/home-tabs.tsx b/apps/www/src/app/(app)/_components/home-tabs.tsx similarity index 91% rename from apps/www/src/app/_components/home-tabs.tsx rename to apps/www/src/app/(app)/_components/home-tabs.tsx index fa604c3be5..c16d3ef258 100644 --- a/apps/www/src/app/_components/home-tabs.tsx +++ b/apps/www/src/app/(app)/_components/home-tabs.tsx @@ -7,8 +7,8 @@ import { Settings2 } from 'lucide-react'; import dynamic from 'next/dynamic'; import { parseAsBoolean, useQueryState } from 'nuqs'; -import { BlockPreview } from '@/components/block-preview'; import { settingsStore } from '@/components/context/settings-store'; +import { PlaygroundPreview } from '@/components/playground-preview'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Button } from '@/registry/default/plate-ui/button'; @@ -64,11 +64,12 @@ export default function HomeTabs() { } }} > - Customize + + Customize - + diff --git a/apps/www/src/app/_components/installation-code.tsx b/apps/www/src/app/(app)/_components/installation-code.tsx similarity index 100% rename from apps/www/src/app/_components/installation-code.tsx rename to apps/www/src/app/(app)/_components/installation-code.tsx diff --git a/apps/www/src/app/_components/installation-tab.tsx b/apps/www/src/app/(app)/_components/installation-tab.tsx similarity index 100% rename from apps/www/src/app/_components/installation-tab.tsx rename to apps/www/src/app/(app)/_components/installation-tab.tsx diff --git a/apps/www/src/app/docs/[[...slug]]/page.tsx b/apps/www/src/app/(app)/docs/[[...slug]]/page.tsx similarity index 91% rename from apps/www/src/app/docs/[[...slug]]/page.tsx rename to apps/www/src/app/(app)/docs/[[...slug]]/page.tsx index 3cd7abce7d..99f6eefd2e 100644 --- a/apps/www/src/app/docs/[[...slug]]/page.tsx +++ b/apps/www/src/app/(app)/docs/[[...slug]]/page.tsx @@ -11,12 +11,12 @@ import { getTableOfContents } from '@/lib/toc'; import '@/styles/mdx.css'; interface DocPageProps { - params: { + params: Promise<{ slug: string[]; - }; + }>; } -function getDocFromParams({ params }: DocPageProps) { +function getDocFromParams({ params }: { params: { slug: string[] } }) { const slug = params.slug?.join('/') || ''; const doc = allDocs.find((_doc) => _doc.slugAsParams === slug); @@ -63,7 +63,7 @@ function getDocFromParams({ params }: DocPageProps) { // }; // } -export function generateStaticParams(): DocPageProps['params'][] { +export function generateStaticParams() { const docs = allDocs.map((doc) => ({ slug: doc.slugAsParams.split('/'), })); @@ -71,7 +71,8 @@ export function generateStaticParams(): DocPageProps['params'][] { return docs; } -export default async function DocPage({ params }: DocPageProps) { +export default async function DocPage(props: DocPageProps) { + const params = await props.params; const name = params.slug?.[0]; const isUI = name === 'components'; diff --git a/apps/www/src/app/docs/examples/server/page.tsx b/apps/www/src/app/(app)/docs/examples/server/page.tsx similarity index 100% rename from apps/www/src/app/docs/examples/server/page.tsx rename to apps/www/src/app/(app)/docs/examples/server/page.tsx diff --git a/apps/www/src/app/docs/layout.tsx b/apps/www/src/app/(app)/docs/layout.tsx similarity index 92% rename from apps/www/src/app/docs/layout.tsx rename to apps/www/src/app/(app)/docs/layout.tsx index 7126e9a836..30683b8e6e 100644 --- a/apps/www/src/app/docs/layout.tsx +++ b/apps/www/src/app/(app)/docs/layout.tsx @@ -11,7 +11,7 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
diff --git a/apps/www/src/app/(app)/layout.tsx b/apps/www/src/app/(app)/layout.tsx new file mode 100644 index 0000000000..1b4be9339e --- /dev/null +++ b/apps/www/src/app/(app)/layout.tsx @@ -0,0 +1,12 @@ +import { SiteFooter } from '@/components/site-footer'; +import { SiteHeader } from '@/components/site-header'; + +export default function AppLayout({ children }: { children: React.ReactNode }) { + return ( + <> + +
{children}
+ + + ); +} diff --git a/apps/www/src/app/page.tsx b/apps/www/src/app/(app)/page.tsx similarity index 64% rename from apps/www/src/app/page.tsx rename to apps/www/src/app/(app)/page.tsx index b62c31e1af..0e159efc8f 100644 --- a/apps/www/src/app/page.tsx +++ b/apps/www/src/app/(app)/page.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import dynamic from 'next/dynamic'; import Link from 'next/link'; -import { Icons } from '@/components/icons'; +import { BlockPreview } from '@/components/block-preview'; import { PageHeader, PageHeaderDescription, @@ -13,9 +13,9 @@ import { ThemesButton } from '@/components/themes-button'; import { siteConfig } from '@/config/site'; import { Button } from '@/registry/default/plate-ui/button'; -import { AnnouncementButton } from './announcement-button'; +import { AnnouncementButton } from './_components/announcement-button'; -import '../../public/r/themes.css'; +import '../../../public/r/themes.css'; const HomeTabs = dynamic(() => import('./_components/home-tabs')); const CustomizerDrawer = dynamic( @@ -34,20 +34,18 @@ export default function IndexPage() {
- Plugin system & primitive component library.
- CLI for styled components. Customizable. Open Source. + Framework · Plugins · Components · Themes
-
- - @@ -56,10 +54,23 @@ export default function IndexPage() {
- + + +
+ +
+ +
); } diff --git a/apps/www/src/app/(blocks)/blocks/playground/page.tsx b/apps/www/src/app/(blocks)/blocks/playground/page.tsx new file mode 100644 index 0000000000..2e273b8682 --- /dev/null +++ b/apps/www/src/app/(blocks)/blocks/playground/page.tsx @@ -0,0 +1,34 @@ +import type { Block } from '@/registry/schema'; + +import { cn } from '@udecode/cn'; + +import PlaygroundDemo from '@/registry/default/example/playground-demo'; + +const block: Block = { + code: '', + highlightedCode: '', + name: 'playground', + type: 'registry:block', +}; + +export default function PlaygroundPage() { + return ( +
+ {/* */} + + {/* {chunks?.map((chunk, index) => ( + + + + ))} */} + {/* */} +
+ ); +} diff --git a/apps/www/src/app/announcement-button.tsx b/apps/www/src/app/announcement-button.tsx deleted file mode 100644 index 0de0181cf7..0000000000 --- a/apps/www/src/app/announcement-button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import * as React from 'react'; - -import { ArrowRightIcon } from 'lucide-react'; - -import { settingsStore } from '@/components/context/settings-store'; -import { Button } from '@/registry/default/plate-ui/button'; -import { Separator } from '@/registry/default/plate-ui/separator'; - -export function AnnouncementButton() { - return ( - - ); -} diff --git a/apps/www/src/app/layout.tsx b/apps/www/src/app/layout.tsx index 604f0bfeeb..7d3053ab8b 100644 --- a/apps/www/src/app/layout.tsx +++ b/apps/www/src/app/layout.tsx @@ -3,11 +3,10 @@ import React from 'react'; import type { Metadata, Viewport } from 'next'; import { cn } from '@udecode/cn'; +import { NuqsAdapter } from 'nuqs/adapters/next/app'; import { GA } from '@/components/analytics/ga'; import { Providers } from '@/components/context/providers'; -import { SiteFooter } from '@/components/site-footer'; -import { SiteHeader } from '@/components/site-header'; import { TailwindIndicator } from '@/components/tailwind-indicator'; import { Toaster } from '@/components/ui/sonner'; import { siteConfig } from '@/config/site'; @@ -93,15 +92,16 @@ export default function RootLayout({ children }: RootLayoutProps) { )} suppressHydrationWarning > - -
-
- -
{children}
- + + +
+
+ {children} +
-
- + + + diff --git a/apps/www/src/components/_block-display.tsx b/apps/www/src/components/_block-display.tsx index f319447735..7981dad23e 100644 --- a/apps/www/src/components/_block-display.tsx +++ b/apps/www/src/components/_block-display.tsx @@ -1,7 +1,7 @@ import { getBlock } from '@/lib/_blocks'; import { styles } from '@/registry/registry-styles'; -import { BlockPreview } from './block-preview'; +import { PlaygroundPreview } from './playground-preview'; export async function BlockDisplay({ name }: { name: string }) { const blocks = await Promise.all( @@ -25,6 +25,6 @@ export async function BlockDisplay({ name }: { name: string }) { } return blocks.map((block) => ( - + )); } diff --git a/apps/www/src/components/block-preview.tsx b/apps/www/src/components/block-preview.tsx index 76f33efa9b..ae8fdfe100 100644 --- a/apps/www/src/components/block-preview.tsx +++ b/apps/www/src/components/block-preview.tsx @@ -1,6 +1,5 @@ 'use client'; -import { type ComponentProps, useState } from 'react'; import * as React from 'react'; import type { Block } from '@/registry/schema'; @@ -8,131 +7,69 @@ import type { ImperativePanelHandle } from 'react-resizable-panels'; import { cn } from '@udecode/cn'; -import { useLiftMode } from '@/hooks/use-lift-mode'; -import PlaygroundDemo from '@/registry/default/example/playground-demo'; - import { BlockToolbar } from './block-toolbar'; -import { ThemeWrapper } from './theme-wrapper'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from './ui/resizable'; -const block: any = { - name: 'playground', -}; - -// TODO: sync export function BlockPreview({ - children, - className, - ...props + block, }: { - block?: Block; -} & ComponentProps<'div'>) { - const { isLiftMode } = useLiftMode(block.name); - // const [isLoading, setIsLoading] = React.useState(true); + block: Pick< + Block, + 'container' | 'description' | 'descriptionSrc' | 'name' | 'src' + >; +}) { const ref = React.useRef(null); - const [fullScreen, setFullScreen] = useState(false); return (
- - - {fullScreen && ( - - + + + {/* {block.name} - - )} - - {!fullScreen && ( - <> -
-
- - - -
-
- -
- - -
- - - -
- - {/* {isLoading ? ( */} - {/*
*/} - {/* */} - {/* Loading... */} - {/*
*/} - {/* ) : null} */} - {/* + ); +} diff --git a/apps/www/src/registry/default/example/mode-toggle.tsx b/apps/www/src/registry/default/example/mode-toggle.tsx index 894f734da9..fbdd5b9f1e 100644 --- a/apps/www/src/registry/default/example/mode-toggle.tsx +++ b/apps/www/src/registry/default/example/mode-toggle.tsx @@ -5,11 +5,14 @@ import * as React from 'react'; import { useTheme } from 'next-themes'; import { Icons } from '@/components/icons'; +import { useMounted } from '@/registry/default/hooks/use-mounted'; import { Button } from '@/registry/default/plate-ui/button'; export default function ModeToggle() { const { setTheme, theme } = useTheme(); + const mounted = useMounted(); + return (