prosekit-registry 0.0.9 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/r/lit-example-minimal.json +1 -1
- package/dist/r/lit-example-slash-menu.json +1 -1
- package/dist/r/lit-example-table.json +1 -1
- package/dist/r/lit-example-toolbar.json +1 -1
- package/dist/r/lit-sample-sample-doc-table.json +1 -1
- package/dist/r/lit-sample-sample-uploader.json +1 -1
- package/dist/r/lit-ui-button.json +1 -1
- package/dist/r/lit-ui-editor-context.json +1 -1
- package/dist/r/lit-ui-image-upload-popover.json +1 -1
- package/dist/r/lit-ui-slash-menu.json +1 -1
- package/dist/r/lit-ui-table-handle.json +1 -1
- package/dist/r/lit-ui-toolbar.json +1 -1
- package/dist/r/preact-example-block-handle.json +1 -1
- package/dist/r/preact-example-blockquote.json +1 -1
- package/dist/r/preact-example-bold.json +1 -1
- package/dist/r/preact-example-change-tracking.json +1 -1
- package/dist/r/preact-example-code-block-themes.json +1 -1
- package/dist/r/preact-example-code-block.json +1 -1
- package/dist/r/preact-example-code.json +1 -1
- package/dist/r/preact-example-drop-cursor.json +1 -1
- package/dist/r/preact-example-emoji-rules.json +1 -1
- package/dist/r/preact-example-full.json +1 -1
- package/dist/r/preact-example-gap-cursor.json +1 -1
- package/dist/r/preact-example-hard-break.json +1 -1
- package/dist/r/preact-example-heading.json +1 -1
- package/dist/r/preact-example-horizontal-rule.json +1 -1
- package/dist/r/preact-example-image-view.json +1 -1
- package/dist/r/preact-example-inline-menu.json +1 -1
- package/dist/r/preact-example-italic.json +1 -1
- package/dist/r/preact-example-keymap.json +1 -1
- package/dist/r/preact-example-link-mark-view.json +1 -1
- package/dist/r/preact-example-link.json +1 -1
- package/dist/r/preact-example-list-custom-checkbox.json +1 -1
- package/dist/r/preact-example-list.json +1 -1
- package/dist/r/preact-example-loro.json +1 -1
- package/dist/r/preact-example-mark-rule.json +1 -1
- package/dist/r/preact-example-minimal.json +1 -1
- package/dist/r/preact-example-placeholder.json +1 -1
- package/dist/r/preact-example-readonly.json +1 -1
- package/dist/r/preact-example-rtl.json +1 -1
- package/dist/r/preact-example-save-html.json +1 -1
- package/dist/r/preact-example-save-json.json +1 -1
- package/dist/r/preact-example-save-markdown.json +1 -1
- package/dist/r/preact-example-search.json +1 -1
- package/dist/r/preact-example-slash-menu.json +1 -1
- package/dist/r/preact-example-strike.json +1 -1
- package/dist/r/preact-example-table.json +1 -1
- package/dist/r/preact-example-temml.json +1 -1
- package/dist/r/preact-example-text-align.json +1 -1
- package/dist/r/preact-example-text-color.json +1 -1
- package/dist/r/preact-example-toolbar.json +1 -1
- package/dist/r/preact-example-typography.json +1 -1
- package/dist/r/preact-example-underline.json +1 -1
- package/dist/r/preact-example-unmount.json +1 -1
- package/dist/r/preact-example-user-menu-dynamic.json +1 -1
- package/dist/r/preact-example-user-menu.json +1 -1
- package/dist/r/preact-example-view-adapter.json +1 -1
- package/dist/r/preact-example-word-counter.json +1 -1
- package/dist/r/preact-example-yjs.json +1 -1
- package/dist/r/preact-sample-define-atom-block.json +1 -1
- package/dist/r/preact-sample-sample-doc-block-handle.json +1 -1
- package/dist/r/preact-sample-sample-doc-bold.json +1 -1
- package/dist/r/preact-sample-sample-doc-code-block.json +1 -1
- package/dist/r/preact-sample-sample-doc-code.json +1 -1
- package/dist/r/preact-sample-sample-doc-drop-cursor.json +1 -1
- package/dist/r/preact-sample-sample-doc-full.json +1 -1
- package/dist/r/preact-sample-sample-doc-gap-cursor.json +1 -1
- package/dist/r/preact-sample-sample-doc-hard-break.json +1 -1
- package/dist/r/preact-sample-sample-doc-heading.json +1 -1
- package/dist/r/preact-sample-sample-doc-image.json +1 -1
- package/dist/r/preact-sample-sample-doc-inline-menu.json +1 -1
- package/dist/r/preact-sample-sample-doc-italic.json +1 -1
- package/dist/r/preact-sample-sample-doc-link-mark-view.json +1 -1
- package/dist/r/preact-sample-sample-doc-link.json +1 -1
- package/dist/r/preact-sample-sample-doc-list-custom-checkbox.json +1 -1
- package/dist/r/preact-sample-sample-doc-list.json +1 -1
- package/dist/r/preact-sample-sample-doc-readonly.json +1 -1
- package/dist/r/preact-sample-sample-doc-rtl.json +1 -1
- package/dist/r/preact-sample-sample-doc-search.json +1 -1
- package/dist/r/preact-sample-sample-doc-strike.json +1 -1
- package/dist/r/preact-sample-sample-doc-table.json +1 -1
- package/dist/r/preact-sample-sample-doc-tex.json +1 -1
- package/dist/r/preact-sample-sample-doc-text-align.json +1 -1
- package/dist/r/preact-sample-sample-doc-text-color.json +1 -1
- package/dist/r/preact-sample-sample-doc-typography.json +1 -1
- package/dist/r/preact-sample-sample-doc-underline.json +1 -1
- package/dist/r/preact-sample-sample-doc-view-adapter.json +1 -1
- package/dist/r/preact-sample-sample-doc-word-counter.json +1 -1
- package/dist/r/preact-sample-sample-uploader.json +1 -1
- package/dist/r/preact-ui-block-handle.json +1 -1
- package/dist/r/preact-ui-button.json +1 -1
- package/dist/r/preact-ui-code-block-view.json +1 -1
- package/dist/r/preact-ui-drop-indicator.json +1 -1
- package/dist/r/preact-ui-image-upload-popover.json +1 -1
- package/dist/r/preact-ui-image-view.json +1 -1
- package/dist/r/preact-ui-inline-menu.json +1 -1
- package/dist/r/preact-ui-search.json +1 -1
- package/dist/r/preact-ui-slash-menu.json +1 -1
- package/dist/r/preact-ui-table-handle.json +1 -1
- package/dist/r/preact-ui-tag-menu.json +1 -1
- package/dist/r/preact-ui-toolbar.json +1 -1
- package/dist/r/preact-ui-user-menu.json +1 -1
- package/dist/r/preact-ui-word-counter.json +1 -1
- package/dist/r/react-example-block-handle.json +1 -1
- package/dist/r/react-example-blockquote.json +1 -1
- package/dist/r/react-example-bold.json +1 -1
- package/dist/r/react-example-change-tracking.json +3 -3
- package/dist/r/react-example-code-block-themes.json +3 -3
- package/dist/r/react-example-code-block.json +1 -1
- package/dist/r/react-example-code.json +1 -1
- package/dist/r/react-example-drop-cursor.json +1 -1
- package/dist/r/react-example-emoji-rules.json +1 -1
- package/dist/r/react-example-full.json +1 -1
- package/dist/r/react-example-gap-cursor.json +1 -1
- package/dist/r/react-example-hard-break.json +2 -2
- package/dist/r/react-example-heading.json +1 -1
- package/dist/r/react-example-horizontal-rule.json +1 -1
- package/dist/r/react-example-image-view.json +1 -1
- package/dist/r/react-example-inline-menu.json +1 -1
- package/dist/r/react-example-italic.json +1 -1
- package/dist/r/react-example-keymap.json +2 -2
- package/dist/r/react-example-link-mark-view.json +2 -2
- package/dist/r/react-example-link.json +1 -1
- package/dist/r/react-example-list-custom-checkbox.json +1 -1
- package/dist/r/react-example-list.json +1 -1
- package/dist/r/react-example-loro.json +2 -2
- package/dist/r/react-example-mark-rule.json +1 -1
- package/dist/r/react-example-minimal.json +1 -1
- package/dist/r/react-example-notion.json +9 -9
- package/dist/r/react-example-page.json +2 -2
- package/dist/r/react-example-placeholder.json +1 -1
- package/dist/r/react-example-readonly.json +2 -2
- package/dist/r/react-example-rtl.json +1 -1
- package/dist/r/react-example-save-html.json +1 -1
- package/dist/r/react-example-save-json.json +1 -1
- package/dist/r/react-example-save-markdown.json +1 -1
- package/dist/r/react-example-search.json +1 -1
- package/dist/r/react-example-slash-menu.json +1 -1
- package/dist/r/react-example-strike.json +2 -2
- package/dist/r/react-example-table.json +1 -1
- package/dist/r/react-example-temml.json +1 -1
- package/dist/r/react-example-text-align.json +2 -2
- package/dist/r/react-example-text-color.json +2 -2
- package/dist/r/react-example-toolbar.json +1 -1
- package/dist/r/react-example-tweet.json +3 -3
- package/dist/r/react-example-typography.json +1 -1
- package/dist/r/react-example-underline.json +1 -1
- package/dist/r/react-example-unmount.json +3 -3
- package/dist/r/react-example-user-menu-dynamic.json +2 -2
- package/dist/r/react-example-user-menu.json +1 -1
- package/dist/r/react-example-view-adapter.json +2 -2
- package/dist/r/react-example-word-counter.json +1 -1
- package/dist/r/react-example-yjs.json +2 -2
- package/dist/r/react-sample-define-atom-block.json +1 -1
- package/dist/r/react-sample-sample-doc-block-handle.json +1 -1
- package/dist/r/react-sample-sample-doc-bold.json +1 -1
- package/dist/r/react-sample-sample-doc-code-block.json +1 -1
- package/dist/r/react-sample-sample-doc-code.json +1 -1
- package/dist/r/react-sample-sample-doc-drop-cursor.json +1 -1
- package/dist/r/react-sample-sample-doc-full.json +1 -1
- package/dist/r/react-sample-sample-doc-gap-cursor.json +1 -1
- package/dist/r/react-sample-sample-doc-hard-break.json +1 -1
- package/dist/r/react-sample-sample-doc-heading.json +1 -1
- package/dist/r/react-sample-sample-doc-image.json +1 -1
- package/dist/r/react-sample-sample-doc-inline-menu.json +1 -1
- package/dist/r/react-sample-sample-doc-italic.json +1 -1
- package/dist/r/react-sample-sample-doc-link-mark-view.json +1 -1
- package/dist/r/react-sample-sample-doc-link.json +1 -1
- package/dist/r/react-sample-sample-doc-list-custom-checkbox.json +1 -1
- package/dist/r/react-sample-sample-doc-list.json +1 -1
- package/dist/r/react-sample-sample-doc-notion.json +1 -1
- package/dist/r/react-sample-sample-doc-page.json +1 -1
- package/dist/r/react-sample-sample-doc-readonly.json +1 -1
- package/dist/r/react-sample-sample-doc-rtl.json +1 -1
- package/dist/r/react-sample-sample-doc-search.json +1 -1
- package/dist/r/react-sample-sample-doc-strike.json +1 -1
- package/dist/r/react-sample-sample-doc-table.json +1 -1
- package/dist/r/react-sample-sample-doc-tex.json +1 -1
- package/dist/r/react-sample-sample-doc-text-align.json +1 -1
- package/dist/r/react-sample-sample-doc-text-color.json +1 -1
- package/dist/r/react-sample-sample-doc-tweet.json +1 -1
- package/dist/r/react-sample-sample-doc-typography.json +1 -1
- package/dist/r/react-sample-sample-doc-underline.json +1 -1
- package/dist/r/react-sample-sample-doc-view-adapter.json +1 -1
- package/dist/r/react-sample-sample-doc-word-counter.json +1 -1
- package/dist/r/react-sample-sample-uploader.json +1 -1
- package/dist/r/react-ui-block-handle.json +2 -2
- package/dist/r/react-ui-button.json +2 -2
- package/dist/r/react-ui-code-block-view.json +2 -2
- package/dist/r/react-ui-drop-indicator.json +2 -2
- package/dist/r/react-ui-image-upload-popover.json +2 -2
- package/dist/r/react-ui-image-view.json +2 -2
- package/dist/r/react-ui-inline-menu.json +2 -2
- package/dist/r/react-ui-search.json +2 -2
- package/dist/r/react-ui-slash-menu.json +4 -4
- package/dist/r/react-ui-table-handle.json +2 -2
- package/dist/r/react-ui-tag-menu.json +2 -2
- package/dist/r/react-ui-toolbar.json +2 -2
- package/dist/r/react-ui-user-menu.json +2 -2
- package/dist/r/react-ui-word-counter.json +2 -2
- package/dist/r/registry.json +480 -480
- package/dist/r/solid-example-block-handle.json +1 -1
- package/dist/r/solid-example-blockquote.json +1 -1
- package/dist/r/solid-example-bold.json +1 -1
- package/dist/r/solid-example-change-tracking.json +1 -1
- package/dist/r/solid-example-code-block-themes.json +1 -1
- package/dist/r/solid-example-code-block.json +1 -1
- package/dist/r/solid-example-code.json +1 -1
- package/dist/r/solid-example-drop-cursor.json +1 -1
- package/dist/r/solid-example-emoji-rules.json +1 -1
- package/dist/r/solid-example-full.json +1 -1
- package/dist/r/solid-example-gap-cursor.json +1 -1
- package/dist/r/solid-example-hard-break.json +1 -1
- package/dist/r/solid-example-heading.json +1 -1
- package/dist/r/solid-example-horizontal-rule.json +1 -1
- package/dist/r/solid-example-image-view.json +1 -1
- package/dist/r/solid-example-inline-menu.json +1 -1
- package/dist/r/solid-example-italic.json +1 -1
- package/dist/r/solid-example-keymap.json +1 -1
- package/dist/r/solid-example-link-mark-view.json +1 -1
- package/dist/r/solid-example-link.json +1 -1
- package/dist/r/solid-example-list-custom-checkbox.json +1 -1
- package/dist/r/solid-example-list.json +1 -1
- package/dist/r/solid-example-loro.json +1 -1
- package/dist/r/solid-example-mark-rule.json +1 -1
- package/dist/r/solid-example-minimal.json +1 -1
- package/dist/r/solid-example-placeholder.json +1 -1
- package/dist/r/solid-example-readonly.json +1 -1
- package/dist/r/solid-example-rtl.json +1 -1
- package/dist/r/solid-example-save-html.json +1 -1
- package/dist/r/solid-example-save-json.json +1 -1
- package/dist/r/solid-example-save-markdown.json +1 -1
- package/dist/r/solid-example-search.json +1 -1
- package/dist/r/solid-example-slash-menu.json +1 -1
- package/dist/r/solid-example-strike.json +1 -1
- package/dist/r/solid-example-table.json +1 -1
- package/dist/r/solid-example-temml.json +1 -1
- package/dist/r/solid-example-text-align.json +1 -1
- package/dist/r/solid-example-text-color.json +1 -1
- package/dist/r/solid-example-toolbar.json +1 -1
- package/dist/r/solid-example-typography.json +1 -1
- package/dist/r/solid-example-underline.json +1 -1
- package/dist/r/solid-example-unmount.json +1 -1
- package/dist/r/solid-example-user-menu-dynamic.json +1 -1
- package/dist/r/solid-example-user-menu.json +1 -1
- package/dist/r/solid-example-view-adapter.json +1 -1
- package/dist/r/solid-example-word-counter.json +1 -1
- package/dist/r/solid-example-yjs.json +1 -1
- package/dist/r/solid-sample-define-atom-block.json +1 -1
- package/dist/r/solid-sample-sample-doc-block-handle.json +1 -1
- package/dist/r/solid-sample-sample-doc-bold.json +1 -1
- package/dist/r/solid-sample-sample-doc-code-block.json +1 -1
- package/dist/r/solid-sample-sample-doc-code.json +1 -1
- package/dist/r/solid-sample-sample-doc-drop-cursor.json +1 -1
- package/dist/r/solid-sample-sample-doc-full.json +1 -1
- package/dist/r/solid-sample-sample-doc-gap-cursor.json +1 -1
- package/dist/r/solid-sample-sample-doc-hard-break.json +1 -1
- package/dist/r/solid-sample-sample-doc-heading.json +1 -1
- package/dist/r/solid-sample-sample-doc-image.json +1 -1
- package/dist/r/solid-sample-sample-doc-inline-menu.json +1 -1
- package/dist/r/solid-sample-sample-doc-italic.json +1 -1
- package/dist/r/solid-sample-sample-doc-link-mark-view.json +1 -1
- package/dist/r/solid-sample-sample-doc-link.json +1 -1
- package/dist/r/solid-sample-sample-doc-list-custom-checkbox.json +1 -1
- package/dist/r/solid-sample-sample-doc-list.json +1 -1
- package/dist/r/solid-sample-sample-doc-readonly.json +1 -1
- package/dist/r/solid-sample-sample-doc-rtl.json +1 -1
- package/dist/r/solid-sample-sample-doc-search.json +1 -1
- package/dist/r/solid-sample-sample-doc-strike.json +1 -1
- package/dist/r/solid-sample-sample-doc-table.json +1 -1
- package/dist/r/solid-sample-sample-doc-tex.json +1 -1
- package/dist/r/solid-sample-sample-doc-text-align.json +1 -1
- package/dist/r/solid-sample-sample-doc-text-color.json +1 -1
- package/dist/r/solid-sample-sample-doc-typography.json +1 -1
- package/dist/r/solid-sample-sample-doc-underline.json +1 -1
- package/dist/r/solid-sample-sample-doc-view-adapter.json +1 -1
- package/dist/r/solid-sample-sample-doc-word-counter.json +1 -1
- package/dist/r/solid-sample-sample-uploader.json +1 -1
- package/dist/r/solid-ui-block-handle.json +1 -1
- package/dist/r/solid-ui-button.json +1 -1
- package/dist/r/solid-ui-code-block-view.json +1 -1
- package/dist/r/solid-ui-drop-indicator.json +1 -1
- package/dist/r/solid-ui-image-upload-popover.json +1 -1
- package/dist/r/solid-ui-image-view.json +1 -1
- package/dist/r/solid-ui-inline-menu.json +1 -1
- package/dist/r/solid-ui-search.json +1 -1
- package/dist/r/solid-ui-slash-menu.json +1 -1
- package/dist/r/solid-ui-table-handle.json +1 -1
- package/dist/r/solid-ui-tag-menu.json +1 -1
- package/dist/r/solid-ui-toolbar.json +1 -1
- package/dist/r/solid-ui-user-menu.json +1 -1
- package/dist/r/solid-ui-word-counter.json +1 -1
- package/dist/r/svelte-example-block-handle.json +1 -1
- package/dist/r/svelte-example-blockquote.json +1 -1
- package/dist/r/svelte-example-bold.json +1 -1
- package/dist/r/svelte-example-change-tracking.json +1 -1
- package/dist/r/svelte-example-code-block-themes.json +1 -1
- package/dist/r/svelte-example-code-block.json +1 -1
- package/dist/r/svelte-example-code.json +1 -1
- package/dist/r/svelte-example-drop-cursor.json +1 -1
- package/dist/r/svelte-example-emoji-rules.json +1 -1
- package/dist/r/svelte-example-full.json +1 -1
- package/dist/r/svelte-example-gap-cursor.json +1 -1
- package/dist/r/svelte-example-hard-break.json +1 -1
- package/dist/r/svelte-example-heading.json +1 -1
- package/dist/r/svelte-example-horizontal-rule.json +1 -1
- package/dist/r/svelte-example-image-view.json +1 -1
- package/dist/r/svelte-example-inline-menu.json +1 -1
- package/dist/r/svelte-example-italic.json +1 -1
- package/dist/r/svelte-example-katex.json +1 -1
- package/dist/r/svelte-example-keymap.json +1 -1
- package/dist/r/svelte-example-link-mark-view.json +1 -1
- package/dist/r/svelte-example-link.json +1 -1
- package/dist/r/svelte-example-list-custom-checkbox.json +1 -1
- package/dist/r/svelte-example-list.json +1 -1
- package/dist/r/svelte-example-loro.json +1 -1
- package/dist/r/svelte-example-mark-rule.json +1 -1
- package/dist/r/svelte-example-minimal.json +1 -1
- package/dist/r/svelte-example-page.json +1 -1
- package/dist/r/svelte-example-placeholder.json +1 -1
- package/dist/r/svelte-example-readonly.json +1 -1
- package/dist/r/svelte-example-rtl.json +1 -1
- package/dist/r/svelte-example-save-html.json +1 -1
- package/dist/r/svelte-example-save-json.json +1 -1
- package/dist/r/svelte-example-save-markdown.json +1 -1
- package/dist/r/svelte-example-search.json +1 -1
- package/dist/r/svelte-example-slash-menu.json +1 -1
- package/dist/r/svelte-example-strike.json +1 -1
- package/dist/r/svelte-example-table.json +1 -1
- package/dist/r/svelte-example-text-align.json +1 -1
- package/dist/r/svelte-example-text-color.json +1 -1
- package/dist/r/svelte-example-toolbar.json +1 -1
- package/dist/r/svelte-example-typography.json +1 -1
- package/dist/r/svelte-example-underline.json +1 -1
- package/dist/r/svelte-example-unmount.json +1 -1
- package/dist/r/svelte-example-user-menu-dynamic.json +1 -1
- package/dist/r/svelte-example-user-menu.json +1 -1
- package/dist/r/svelte-example-view-adapter.json +1 -1
- package/dist/r/svelte-example-word-counter.json +1 -1
- package/dist/r/svelte-example-yjs.json +1 -1
- package/dist/r/svelte-sample-define-atom-block.json +1 -1
- package/dist/r/svelte-sample-sample-doc-block-handle.json +1 -1
- package/dist/r/svelte-sample-sample-doc-bold.json +1 -1
- package/dist/r/svelte-sample-sample-doc-code-block.json +1 -1
- package/dist/r/svelte-sample-sample-doc-code.json +1 -1
- package/dist/r/svelte-sample-sample-doc-drop-cursor.json +1 -1
- package/dist/r/svelte-sample-sample-doc-full.json +1 -1
- package/dist/r/svelte-sample-sample-doc-gap-cursor.json +1 -1
- package/dist/r/svelte-sample-sample-doc-hard-break.json +1 -1
- package/dist/r/svelte-sample-sample-doc-heading.json +1 -1
- package/dist/r/svelte-sample-sample-doc-image.json +1 -1
- package/dist/r/svelte-sample-sample-doc-inline-menu.json +1 -1
- package/dist/r/svelte-sample-sample-doc-italic.json +1 -1
- package/dist/r/svelte-sample-sample-doc-link-mark-view.json +1 -1
- package/dist/r/svelte-sample-sample-doc-link.json +1 -1
- package/dist/r/svelte-sample-sample-doc-list-custom-checkbox.json +1 -1
- package/dist/r/svelte-sample-sample-doc-list.json +1 -1
- package/dist/r/svelte-sample-sample-doc-page.json +1 -1
- package/dist/r/svelte-sample-sample-doc-readonly.json +1 -1
- package/dist/r/svelte-sample-sample-doc-rtl.json +1 -1
- package/dist/r/svelte-sample-sample-doc-search.json +1 -1
- package/dist/r/svelte-sample-sample-doc-strike.json +1 -1
- package/dist/r/svelte-sample-sample-doc-table.json +1 -1
- package/dist/r/svelte-sample-sample-doc-tex.json +1 -1
- package/dist/r/svelte-sample-sample-doc-text-align.json +1 -1
- package/dist/r/svelte-sample-sample-doc-text-color.json +1 -1
- package/dist/r/svelte-sample-sample-doc-typography.json +1 -1
- package/dist/r/svelte-sample-sample-doc-underline.json +1 -1
- package/dist/r/svelte-sample-sample-doc-view-adapter.json +1 -1
- package/dist/r/svelte-sample-sample-doc-word-counter.json +1 -1
- package/dist/r/svelte-sample-sample-uploader.json +1 -1
- package/dist/r/svelte-ui-block-handle.json +1 -1
- package/dist/r/svelte-ui-button.json +1 -1
- package/dist/r/svelte-ui-code-block-view.json +1 -1
- package/dist/r/svelte-ui-drop-indicator.json +1 -1
- package/dist/r/svelte-ui-image-upload-popover.json +1 -1
- package/dist/r/svelte-ui-image-view.json +1 -1
- package/dist/r/svelte-ui-inline-menu.json +1 -1
- package/dist/r/svelte-ui-search.json +1 -1
- package/dist/r/svelte-ui-slash-menu.json +1 -1
- package/dist/r/svelte-ui-table-handle.json +1 -1
- package/dist/r/svelte-ui-tag-menu.json +1 -1
- package/dist/r/svelte-ui-toolbar.json +1 -1
- package/dist/r/svelte-ui-user-menu.json +1 -1
- package/dist/r/svelte-ui-word-counter.json +1 -1
- package/dist/r/vanilla-example-minimal.json +1 -1
- package/dist/r/vanilla-example-slash-menu.json +1 -1
- package/dist/r/vanilla-ui-slash-menu.json +1 -1
- package/dist/r/vue-example-block-handle.json +1 -1
- package/dist/r/vue-example-blockquote.json +1 -1
- package/dist/r/vue-example-bold.json +1 -1
- package/dist/r/vue-example-change-tracking.json +1 -1
- package/dist/r/vue-example-code-block-themes.json +1 -1
- package/dist/r/vue-example-code-block.json +1 -1
- package/dist/r/vue-example-code.json +1 -1
- package/dist/r/vue-example-drop-cursor.json +1 -1
- package/dist/r/vue-example-emoji-rules.json +1 -1
- package/dist/r/vue-example-full.json +1 -1
- package/dist/r/vue-example-gap-cursor.json +1 -1
- package/dist/r/vue-example-hard-break.json +1 -1
- package/dist/r/vue-example-heading.json +1 -1
- package/dist/r/vue-example-horizontal-rule.json +1 -1
- package/dist/r/vue-example-image-view.json +1 -1
- package/dist/r/vue-example-inline-menu.json +1 -1
- package/dist/r/vue-example-italic.json +1 -1
- package/dist/r/vue-example-katex.json +1 -1
- package/dist/r/vue-example-keymap.json +1 -1
- package/dist/r/vue-example-link-mark-view.json +1 -1
- package/dist/r/vue-example-link.json +1 -1
- package/dist/r/vue-example-list-custom-checkbox.json +1 -1
- package/dist/r/vue-example-list.json +1 -1
- package/dist/r/vue-example-loro.json +1 -1
- package/dist/r/vue-example-mark-rule.json +1 -1
- package/dist/r/vue-example-minimal.json +1 -1
- package/dist/r/vue-example-placeholder.json +1 -1
- package/dist/r/vue-example-readonly.json +1 -1
- package/dist/r/vue-example-rtl.json +1 -1
- package/dist/r/vue-example-save-html.json +1 -1
- package/dist/r/vue-example-save-json.json +1 -1
- package/dist/r/vue-example-save-markdown.json +1 -1
- package/dist/r/vue-example-search.json +1 -1
- package/dist/r/vue-example-slash-menu.json +1 -1
- package/dist/r/vue-example-strike.json +1 -1
- package/dist/r/vue-example-table.json +1 -1
- package/dist/r/vue-example-text-align.json +1 -1
- package/dist/r/vue-example-text-color.json +1 -1
- package/dist/r/vue-example-toolbar.json +1 -1
- package/dist/r/vue-example-tweet.json +1 -1
- package/dist/r/vue-example-typography.json +1 -1
- package/dist/r/vue-example-underline.json +1 -1
- package/dist/r/vue-example-unmount.json +1 -1
- package/dist/r/vue-example-user-menu-dynamic.json +1 -1
- package/dist/r/vue-example-user-menu.json +1 -1
- package/dist/r/vue-example-view-adapter.json +1 -1
- package/dist/r/vue-example-word-counter.json +1 -1
- package/dist/r/vue-example-yjs.json +1 -1
- package/dist/r/vue-sample-define-atom-block.json +1 -1
- package/dist/r/vue-sample-sample-doc-block-handle.json +1 -1
- package/dist/r/vue-sample-sample-doc-bold.json +1 -1
- package/dist/r/vue-sample-sample-doc-code-block.json +1 -1
- package/dist/r/vue-sample-sample-doc-code.json +1 -1
- package/dist/r/vue-sample-sample-doc-drop-cursor.json +1 -1
- package/dist/r/vue-sample-sample-doc-full.json +1 -1
- package/dist/r/vue-sample-sample-doc-gap-cursor.json +1 -1
- package/dist/r/vue-sample-sample-doc-hard-break.json +1 -1
- package/dist/r/vue-sample-sample-doc-heading.json +1 -1
- package/dist/r/vue-sample-sample-doc-image.json +1 -1
- package/dist/r/vue-sample-sample-doc-inline-menu.json +1 -1
- package/dist/r/vue-sample-sample-doc-italic.json +1 -1
- package/dist/r/vue-sample-sample-doc-link-mark-view.json +1 -1
- package/dist/r/vue-sample-sample-doc-link.json +1 -1
- package/dist/r/vue-sample-sample-doc-list-custom-checkbox.json +1 -1
- package/dist/r/vue-sample-sample-doc-list.json +1 -1
- package/dist/r/vue-sample-sample-doc-readonly.json +1 -1
- package/dist/r/vue-sample-sample-doc-rtl.json +1 -1
- package/dist/r/vue-sample-sample-doc-search.json +1 -1
- package/dist/r/vue-sample-sample-doc-strike.json +1 -1
- package/dist/r/vue-sample-sample-doc-table.json +1 -1
- package/dist/r/vue-sample-sample-doc-tex.json +1 -1
- package/dist/r/vue-sample-sample-doc-text-align.json +1 -1
- package/dist/r/vue-sample-sample-doc-text-color.json +1 -1
- package/dist/r/vue-sample-sample-doc-tweet.json +1 -1
- package/dist/r/vue-sample-sample-doc-typography.json +1 -1
- package/dist/r/vue-sample-sample-doc-underline.json +1 -1
- package/dist/r/vue-sample-sample-doc-view-adapter.json +1 -1
- package/dist/r/vue-sample-sample-doc-word-counter.json +1 -1
- package/dist/r/vue-sample-sample-uploader.json +1 -1
- package/dist/r/vue-ui-block-handle.json +1 -1
- package/dist/r/vue-ui-button.json +1 -1
- package/dist/r/vue-ui-code-block-view.json +1 -1
- package/dist/r/vue-ui-drop-indicator.json +1 -1
- package/dist/r/vue-ui-image-upload-popover.json +1 -1
- package/dist/r/vue-ui-image-view.json +1 -1
- package/dist/r/vue-ui-inline-menu.json +1 -1
- package/dist/r/vue-ui-search.json +1 -1
- package/dist/r/vue-ui-slash-menu.json +1 -1
- package/dist/r/vue-ui-table-handle.json +1 -1
- package/dist/r/vue-ui-tag-menu.json +1 -1
- package/dist/r/vue-ui-toolbar.json +1 -1
- package/dist/r/vue-ui-user-menu.json +1 -1
- package/dist/r/vue-ui-word-counter.json +1 -1
- package/package.json +3 -3
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"https://unpkg.com/prosekit-registry/dist/r/react-ui-button.json"
|
|
8
8
|
],
|
|
9
9
|
"dependencies": [
|
|
10
|
-
"prosekit@^0.20.0-beta.
|
|
10
|
+
"prosekit@^0.20.0-beta.5"
|
|
11
11
|
],
|
|
12
12
|
"files": [
|
|
13
13
|
{
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"path": "registry/src/react/examples/keymap/toolbar.tsx",
|
|
33
33
|
"type": "registry:component",
|
|
34
34
|
"target": "components/editor/examples/keymap/toolbar.tsx",
|
|
35
|
-
"content": "'use client'\nimport { useState } from 'react'\n\nimport { Button } from '../../ui/button'\n\nimport { useSubmitKeymap } from './use-submit-keymap'\n\nexport default function Toolbar(props: {\n onSubmit: (hotkey: string) => void\n}) {\n const [hotkey, setHotkey] = useState<'Shift-Enter' | 'Enter'>('Shift-Enter')\n useSubmitKeymap(hotkey, props.onSubmit)\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={hotkey === 'Shift-Enter'}\n onClick={() => setHotkey('Shift-Enter')}\n >\n <span className=\"mr-1\">Submit with</span>\n <kbd>Shift + Enter</kbd>\n </Button>\n\n <Button pressed={hotkey === 'Enter'} onClick={() => setHotkey('Enter')}>\n <span className=\"mr-1\">Submit with</span>\n <kbd>Enter</kbd>\n </Button>\n </div>\n )\n}\n"
|
|
35
|
+
"content": "'use client'\n\nimport { useState } from 'react'\n\nimport { Button } from '../../ui/button'\n\nimport { useSubmitKeymap } from './use-submit-keymap'\n\nexport default function Toolbar(props: {\n onSubmit: (hotkey: string) => void\n}) {\n const [hotkey, setHotkey] = useState<'Shift-Enter' | 'Enter'>('Shift-Enter')\n useSubmitKeymap(hotkey, props.onSubmit)\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={hotkey === 'Shift-Enter'}\n onClick={() => setHotkey('Shift-Enter')}\n >\n <span className=\"mr-1\">Submit with</span>\n <kbd>Shift + Enter</kbd>\n </Button>\n\n <Button pressed={hotkey === 'Enter'} onClick={() => setHotkey('Enter')}>\n <span className=\"mr-1\">Submit with</span>\n <kbd>Enter</kbd>\n </Button>\n </div>\n )\n}\n"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"path": "registry/src/react/examples/keymap/use-submit-keymap.ts",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"https://unpkg.com/prosekit-registry/dist/r/react-sample-sample-doc-link-mark-view.json"
|
|
8
8
|
],
|
|
9
9
|
"dependencies": [
|
|
10
|
-
"prosekit@^0.20.0-beta.
|
|
10
|
+
"prosekit@^0.20.0-beta.5"
|
|
11
11
|
],
|
|
12
12
|
"files": [
|
|
13
13
|
{
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"path": "registry/src/react/examples/link-mark-view/link-view.tsx",
|
|
33
33
|
"type": "registry:component",
|
|
34
34
|
"target": "components/editor/examples/link-mark-view/link-view.tsx",
|
|
35
|
-
"content": "'use client'\nimport type { ReactMarkViewProps } from 'prosekit/react'\nimport { useEffect, useState } from 'react'\n\nconst colors = [\n '#f06292',\n '#ba68c8',\n '#9575cd',\n '#7986cb',\n '#64b5f6',\n '#4fc3f7',\n '#4dd0e1',\n '#4db6ac',\n '#81c784',\n '#aed581',\n '#ffb74d',\n '#ffa726',\n '#ff8a65',\n '#d4e157',\n '#ffd54f',\n '#ffecb3',\n]\n\nfunction pickRandomColor() {\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\nexport default function Link(props: ReactMarkViewProps) {\n const [color, setColor] = useState(colors[0])\n const href = props.mark.attrs.href as string\n\n useEffect(() => {\n const interval = setInterval(() => {\n setColor(pickRandomColor())\n }, 1000)\n return () => clearInterval(interval)\n }, [])\n\n return (\n <a\n href={href}\n ref={props.contentRef}\n style={{ color, transition: 'color 1s ease-in-out' }}\n >\n </a>\n )\n}\n"
|
|
35
|
+
"content": "'use client'\n\nimport type { ReactMarkViewProps } from 'prosekit/react'\nimport { useEffect, useState } from 'react'\n\nconst colors = [\n '#f06292',\n '#ba68c8',\n '#9575cd',\n '#7986cb',\n '#64b5f6',\n '#4fc3f7',\n '#4dd0e1',\n '#4db6ac',\n '#81c784',\n '#aed581',\n '#ffb74d',\n '#ffa726',\n '#ff8a65',\n '#d4e157',\n '#ffd54f',\n '#ffecb3',\n]\n\nfunction pickRandomColor() {\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\nexport default function Link(props: ReactMarkViewProps) {\n const [color, setColor] = useState(colors[0])\n const href = props.mark.attrs.href as string\n\n useEffect(() => {\n const interval = setInterval(() => {\n setColor(pickRandomColor())\n }, 1000)\n return () => clearInterval(interval)\n }, [])\n\n return (\n <a\n href={href}\n ref={props.contentRef}\n style={{ color, transition: 'color 1s ease-in-out' }}\n >\n </a>\n )\n}\n"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
38
|
"meta": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"dependencies": [
|
|
10
10
|
"loro-crdt",
|
|
11
11
|
"loro-prosemirror",
|
|
12
|
-
"prosekit@^0.20.0-beta.
|
|
12
|
+
"prosekit@^0.20.0-beta.5"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
|
16
16
|
"path": "registry/src/react/examples/loro/editor-component.tsx",
|
|
17
17
|
"type": "registry:component",
|
|
18
18
|
"target": "components/editor/examples/loro/editor-component.tsx",
|
|
19
|
-
"content": "'use client'\nimport 'prosekit/basic/style.css'\nimport 'prosekit/basic/typography.css'\nimport 'prosekit/extensions/loro/style.css'\n\nimport type { CursorAwareness, LoroDocType } from 'loro-prosemirror'\nimport { createEditor } from 'prosekit/core'\nimport { ProseKit } from 'prosekit/react'\nimport { useMemo } from 'react'\n\nimport { Toolbar } from '../../ui/toolbar'\n\nimport { defineExtension } from './extension'\n\nexport default function EditorComponent(props: {\n loro: LoroDocType\n awareness: CursorAwareness\n}) {\n const editor = useMemo(() => {\n const extension = defineExtension(props.loro, props.awareness)\n return createEditor({ extension })\n }, [props.loro, props.awareness])\n\n return (\n <ProseKit editor={editor}>\n <div className=\"box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white\">\n <Toolbar />\n <div className=\"relative w-full flex-1 box-border overflow-y-auto\">\n <div ref={editor.mount} className=\"ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500\"></div>\n </div>\n </div>\n </ProseKit>\n )\n}\n"
|
|
19
|
+
"content": "'use client'\n\nimport 'prosekit/basic/style.css'\nimport 'prosekit/basic/typography.css'\nimport 'prosekit/extensions/loro/style.css'\n\nimport type { CursorAwareness, LoroDocType } from 'loro-prosemirror'\nimport { createEditor } from 'prosekit/core'\nimport { ProseKit } from 'prosekit/react'\nimport { useMemo } from 'react'\n\nimport { Toolbar } from '../../ui/toolbar'\n\nimport { defineExtension } from './extension'\n\nexport default function EditorComponent(props: {\n loro: LoroDocType\n awareness: CursorAwareness\n}) {\n const editor = useMemo(() => {\n const extension = defineExtension(props.loro, props.awareness)\n return createEditor({ extension })\n }, [props.loro, props.awareness])\n\n return (\n <ProseKit editor={editor}>\n <div className=\"box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white\">\n <Toolbar />\n <div className=\"relative w-full flex-1 box-border overflow-y-auto\">\n <div ref={editor.mount} className=\"ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500\"></div>\n </div>\n </div>\n </ProseKit>\n )\n}\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "registry/src/react/examples/loro/editor.tsx",
|
|
@@ -19,20 +19,20 @@
|
|
|
19
19
|
"@base-ui/react",
|
|
20
20
|
"@egoist/tailwindcss-icons",
|
|
21
21
|
"@iconify-json/lucide",
|
|
22
|
-
"prosekit@^0.20.0-beta.
|
|
22
|
+
"prosekit@^0.20.0-beta.5"
|
|
23
23
|
],
|
|
24
24
|
"files": [
|
|
25
25
|
{
|
|
26
26
|
"path": "registry/src/react/examples/notion/block-handle-menu.tsx",
|
|
27
27
|
"type": "registry:component",
|
|
28
28
|
"target": "components/editor/examples/notion/block-handle-menu.tsx",
|
|
29
|
-
"content": "'use client'\nimport { Menu } from '@base-ui/react'\nimport type { Editor } from 'prosekit/core'\nimport { clsx } from 'prosekit/core'\nimport type { ListAttrs } from 'prosekit/extensions/list'\nimport { useEditorDerivedValue } from 'prosekit/react'\nimport { useState } from 'react'\n\nimport type { EditorExtension } from './extension'\n\nconst POPUP_CLASSNAME =\n 'origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 w-50'\n\nconst ITEM_CLASSNAME =\n 'flex items-center justify-between gap-2 cursor-default py-2 px-3 text-sm leading-4 outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900'\n\nconst TEXT_COLOR_CLASSNAME = clsx(\n `border rounded-sm relative after:absolute after:inset-0 after:flex after:items-center after:justify-center after:content-['A']`,\n)\n\ninterface Props {\n children: React.ReactElement\n}\n\ninterface SubmenuInfo {\n key: string\n label: string\n iconClassName?: string\n isAvailable: boolean\n children: ItemInfo[]\n}\n\ninterface MenuItemInfo {\n key: string\n label: string\n isActive: boolean\n isAvailable: boolean\n iconClassName?: string\n shortcut?: string\n danger?: boolean\n onClick: () => void\n children?: never\n}\n\ntype ItemInfo = SubmenuInfo | MenuItemInfo\n\nfunction getActiveBlockType(editor: Editor<EditorExtension>) {\n if (editor.nodes.heading.isActive({ level: 1 })) {\n return 'h1'\n }\n\n if (editor.nodes.heading.isActive({ level: 2 })) {\n return 'h2'\n }\n\n if (editor.nodes.heading.isActive({ level: 3 })) {\n return 'h3'\n }\n\n if (editor.nodes.list.isActive({ kind: 'bullet' })) {\n return 'bullet-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'ordered' })) {\n return 'ordered-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'task' })) {\n return 'task-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'toggle' })) {\n return 'toggle-list'\n }\n\n if (editor.nodes.image.isActive()) {\n return 'image'\n }\n\n return 'text'\n}\n\nfunction turnIntoList(editor: Editor<EditorExtension>, attrs: ListAttrs) {\n editor.commands.setParagraph()\n editor.commands.wrapInList(attrs)\n}\n\nfunction getMenuItems(editor: Editor<EditorExtension>): ItemInfo[] {\n const activeBlockType = getActiveBlockType(editor)\n\n return [\n {\n key: 'turn-into',\n label: 'Turn into',\n iconClassName: 'i-lucide-refresh-cw',\n isAvailable: activeBlockType !== 'image',\n children: [\n {\n key: 'text',\n label: 'Text',\n iconClassName: 'i-lucide-type',\n isActive: activeBlockType === 'text',\n isAvailable: editor.commands.setParagraph.canExec(),\n onClick: () => editor.commands.setParagraph(),\n },\n {\n key: 'h1',\n label: 'Heading 1',\n iconClassName: 'i-lucide-heading-1',\n isActive: activeBlockType === 'h1',\n isAvailable: editor.commands.setHeading.canExec({ level: 1 }),\n onClick: () => editor.commands.setHeading({ level: 1 }),\n },\n {\n key: 'h2',\n label: 'Heading 2',\n iconClassName: 'i-lucide-heading-2',\n isActive: activeBlockType === 'h2',\n isAvailable: editor.commands.setHeading.canExec({ level: 2 }),\n onClick: () => editor.commands.setHeading({ level: 2 }),\n },\n {\n key: 'h3',\n label: 'Heading 3',\n iconClassName: 'i-lucide-heading-3',\n isActive: activeBlockType === 'h3',\n isAvailable: editor.commands.setHeading.canExec({ level: 3 }),\n onClick: () => editor.commands.setHeading({ level: 3 }),\n },\n {\n key: 'bullet-list',\n label: 'Bullet list',\n iconClassName: 'i-lucide-list',\n isActive: activeBlockType === 'bullet-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'bullet' }),\n onClick: () => turnIntoList(editor, { kind: 'bullet' }),\n },\n {\n key: 'ordered-list',\n label: 'Ordered list',\n iconClassName: 'i-lucide-list-ordered',\n isActive: activeBlockType === 'ordered-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'ordered' }),\n onClick: () => turnIntoList(editor, { kind: 'ordered' }),\n },\n {\n key: 'task-list',\n label: 'Task list',\n iconClassName: 'i-lucide-list-checks',\n isActive: activeBlockType === 'task-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'task' }),\n onClick: () => turnIntoList(editor, { kind: 'task' }),\n },\n {\n key: 'toggle-list',\n label: 'Toggle list',\n iconClassName: 'i-lucide-list-collapse',\n isActive: activeBlockType === 'toggle-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'toggle' }),\n onClick: () => turnIntoList(editor, { kind: 'toggle' }),\n },\n ],\n },\n {\n key: 'color',\n label: 'Color',\n iconClassName: 'i-lucide-paint-roller',\n isAvailable: activeBlockType !== 'image',\n children: [\n {\n key: 'default',\n label: 'Default Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-current text-current'),\n isActive: !editor.marks.textColor.isActive(),\n isAvailable: editor.commands.removeTextColor.canExec(),\n onClick: () => editor.commands.removeTextColor(),\n },\n {\n key: 'gray',\n label: 'Gray Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-gray-300 text-gray-500'),\n isActive: editor.marks.textColor.isActive({ color: 'gray' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'gray' }),\n onClick: () => editor.commands.addTextColor({ color: 'gray' }),\n },\n {\n key: 'orange',\n label: 'Orange Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-orange-300 text-orange-500'),\n isActive: editor.marks.textColor.isActive({ color: 'orange' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'orange' }),\n onClick: () => editor.commands.addTextColor({ color: 'orange' }),\n },\n {\n key: 'yellow',\n label: 'Yellow Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-yellow-300 text-yellow-500'),\n isActive: editor.marks.textColor.isActive({ color: 'yellow' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'yellow' }),\n onClick: () => editor.commands.addTextColor({ color: 'yellow' }),\n },\n {\n key: 'green',\n label: 'Green Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-green-300 text-green-500'),\n isActive: editor.marks.textColor.isActive({ color: 'green' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'green' }),\n onClick: () => editor.commands.addTextColor({ color: 'green' }),\n },\n {\n key: 'blue',\n label: 'Blue Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-blue-300 text-blue-500'),\n isActive: editor.marks.textColor.isActive({ color: 'blue' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'blue' }),\n onClick: () => editor.commands.addTextColor({ color: 'blue' }),\n },\n {\n key: 'purple',\n label: 'Purple Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-purple-300 text-purple-500'),\n isActive: editor.marks.textColor.isActive({ color: 'purple' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'purple' }),\n onClick: () => editor.commands.addTextColor({ color: 'purple' }),\n },\n {\n key: 'pink',\n label: 'Pink Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-pink-300 text-pink-500'),\n isActive: editor.marks.textColor.isActive({ color: 'pink' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'pink' }),\n onClick: () => editor.commands.addTextColor({ color: 'pink' }),\n },\n {\n key: 'red',\n label: 'Red Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-red-300 text-red-500'),\n isActive: editor.marks.textColor.isActive({ color: 'red' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'red' }),\n onClick: () => editor.commands.addTextColor({ color: 'red' }),\n },\n ],\n },\n {\n key: 'delete',\n label: 'Delete',\n iconClassName: 'i-lucide-trash-2',\n shortcut: 'Del',\n danger: true,\n isActive: false,\n isAvailable: true,\n onClick: () => editor.view.dispatch(editor.view.state.tr.deleteSelection()),\n },\n ]\n}\n\nfunction BlockHandleItem(props: { item: ItemInfo }) {\n if (!props.item.isAvailable) {\n return null\n } else if (props.item.children) {\n return (\n <Menu.SubmenuRoot>\n <Menu.SubmenuTrigger className={ITEM_CLASSNAME}>\n {props.item.iconClassName && <span className={clsx('inline-block size-4', props.item.iconClassName)} />}\n <span className=\"flex-1\">{props.item.label}</span>\n <span className=\"inline-block size-4 i-lucide-chevron-right opacity-50\">\n </span>\n </Menu.SubmenuTrigger>\n <Menu.Portal>\n <Menu.Positioner align=\"center\">\n <Menu.Popup className={POPUP_CLASSNAME}>\n {props.item.children.map(item => <BlockHandleItem key={item.key} item={item} />)}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.SubmenuRoot>\n )\n } else {\n return (\n <Menu.Item\n className={clsx(ITEM_CLASSNAME, 'group')}\n onClick={props.item.onClick}\n >\n {props.item.iconClassName && <span className={clsx('inline-block size-5', props.item.iconClassName)} />}\n <span className={clsx('flex-1', props.item.danger && 'group-data-highlighted:text-red-500')}>{props.item.label}</span>\n {props.item.isActive && <span className=\"inline-block size-4 i-lucide-check\"></span>}\n {!props.item.isActive && props.item.shortcut && <span className=\"opacity-50\">{props.item.shortcut}</span>}\n </Menu.Item>\n )\n }\n}\n\nexport default function BlockHandleMenu(props: Props) {\n const [open, setOpen] = useState(false)\n\n const items = useEditorDerivedValue(getMenuItems)\n\n return (\n <Menu.Root\n open={open}\n onOpenChange={(open, details) => {\n // ignore the event to open the menu because by default Menu is opened\n // by a `mousedown` event but we only want to open the menu by a `click`\n // event.\n if (open && details.reason === 'trigger-press') {\n return\n }\n setOpen(open)\n }}\n >\n <Menu.Trigger\n render={props.children}\n nativeButton={false}\n onClick={(event) => {\n event.preventDefault()\n setOpen(open => !open)\n }}\n >\n </Menu.Trigger>\n <Menu.Portal>\n <Menu.Backdrop className=\"size-dvw flex fixed inset-0 opacity-0\" />\n <Menu.Positioner className=\"outline-none\" side=\"right\" align=\"center\">\n <Menu.Popup className={POPUP_CLASSNAME}>\n {items.map(item => <BlockHandleItem key={item.key} item={item} />)}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.Root>\n )\n}\n"
|
|
29
|
+
"content": "'use client'\n\nimport { Menu } from '@base-ui/react'\nimport type { Editor } from 'prosekit/core'\nimport { clsx } from 'prosekit/core'\nimport type { ListAttrs } from 'prosekit/extensions/list'\nimport { useEditorDerivedValue } from 'prosekit/react'\nimport { useState } from 'react'\n\nimport type { EditorExtension } from './extension'\n\nconst POPUP_CLASSNAME =\n 'origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 w-50'\n\nconst ITEM_CLASSNAME =\n 'flex items-center justify-between gap-2 cursor-default py-2 px-3 text-sm leading-4 outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900'\n\nconst TEXT_COLOR_CLASSNAME = clsx(\n `border rounded-sm relative after:absolute after:inset-0 after:flex after:items-center after:justify-center after:content-['A']`,\n)\n\ninterface Props {\n children: React.ReactElement\n}\n\ninterface SubmenuInfo {\n key: string\n label: string\n iconClassName?: string\n isAvailable: boolean\n children: ItemInfo[]\n}\n\ninterface MenuItemInfo {\n key: string\n label: string\n isActive: boolean\n isAvailable: boolean\n iconClassName?: string\n shortcut?: string\n danger?: boolean\n onClick: () => void\n children?: never\n}\n\ntype ItemInfo = SubmenuInfo | MenuItemInfo\n\nfunction getActiveBlockType(editor: Editor<EditorExtension>) {\n if (editor.nodes.heading.isActive({ level: 1 })) {\n return 'h1'\n }\n\n if (editor.nodes.heading.isActive({ level: 2 })) {\n return 'h2'\n }\n\n if (editor.nodes.heading.isActive({ level: 3 })) {\n return 'h3'\n }\n\n if (editor.nodes.list.isActive({ kind: 'bullet' })) {\n return 'bullet-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'ordered' })) {\n return 'ordered-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'task' })) {\n return 'task-list'\n }\n\n if (editor.nodes.list.isActive({ kind: 'toggle' })) {\n return 'toggle-list'\n }\n\n if (editor.nodes.image.isActive()) {\n return 'image'\n }\n\n return 'text'\n}\n\nfunction turnIntoList(editor: Editor<EditorExtension>, attrs: ListAttrs) {\n editor.commands.setParagraph()\n editor.commands.wrapInList(attrs)\n}\n\nfunction getMenuItems(editor: Editor<EditorExtension>): ItemInfo[] {\n const activeBlockType = getActiveBlockType(editor)\n\n return [\n {\n key: 'turn-into',\n label: 'Turn into',\n iconClassName: 'i-lucide-refresh-cw',\n isAvailable: activeBlockType !== 'image',\n children: [\n {\n key: 'text',\n label: 'Text',\n iconClassName: 'i-lucide-type',\n isActive: activeBlockType === 'text',\n isAvailable: editor.commands.setParagraph.canExec(),\n onClick: () => editor.commands.setParagraph(),\n },\n {\n key: 'h1',\n label: 'Heading 1',\n iconClassName: 'i-lucide-heading-1',\n isActive: activeBlockType === 'h1',\n isAvailable: editor.commands.setHeading.canExec({ level: 1 }),\n onClick: () => editor.commands.setHeading({ level: 1 }),\n },\n {\n key: 'h2',\n label: 'Heading 2',\n iconClassName: 'i-lucide-heading-2',\n isActive: activeBlockType === 'h2',\n isAvailable: editor.commands.setHeading.canExec({ level: 2 }),\n onClick: () => editor.commands.setHeading({ level: 2 }),\n },\n {\n key: 'h3',\n label: 'Heading 3',\n iconClassName: 'i-lucide-heading-3',\n isActive: activeBlockType === 'h3',\n isAvailable: editor.commands.setHeading.canExec({ level: 3 }),\n onClick: () => editor.commands.setHeading({ level: 3 }),\n },\n {\n key: 'bullet-list',\n label: 'Bullet list',\n iconClassName: 'i-lucide-list',\n isActive: activeBlockType === 'bullet-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'bullet' }),\n onClick: () => turnIntoList(editor, { kind: 'bullet' }),\n },\n {\n key: 'ordered-list',\n label: 'Ordered list',\n iconClassName: 'i-lucide-list-ordered',\n isActive: activeBlockType === 'ordered-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'ordered' }),\n onClick: () => turnIntoList(editor, { kind: 'ordered' }),\n },\n {\n key: 'task-list',\n label: 'Task list',\n iconClassName: 'i-lucide-list-checks',\n isActive: activeBlockType === 'task-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'task' }),\n onClick: () => turnIntoList(editor, { kind: 'task' }),\n },\n {\n key: 'toggle-list',\n label: 'Toggle list',\n iconClassName: 'i-lucide-list-collapse',\n isActive: activeBlockType === 'toggle-list',\n isAvailable: editor.commands.wrapInList.canExec({ kind: 'toggle' }),\n onClick: () => turnIntoList(editor, { kind: 'toggle' }),\n },\n ],\n },\n {\n key: 'color',\n label: 'Color',\n iconClassName: 'i-lucide-paint-roller',\n isAvailable: activeBlockType !== 'image',\n children: [\n {\n key: 'default',\n label: 'Default Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-current text-current'),\n isActive: !editor.marks.textColor.isActive(),\n isAvailable: editor.commands.removeTextColor.canExec(),\n onClick: () => editor.commands.removeTextColor(),\n },\n {\n key: 'gray',\n label: 'Gray Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-gray-300 text-gray-500'),\n isActive: editor.marks.textColor.isActive({ color: 'gray' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'gray' }),\n onClick: () => editor.commands.addTextColor({ color: 'gray' }),\n },\n {\n key: 'orange',\n label: 'Orange Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-orange-300 text-orange-500'),\n isActive: editor.marks.textColor.isActive({ color: 'orange' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'orange' }),\n onClick: () => editor.commands.addTextColor({ color: 'orange' }),\n },\n {\n key: 'yellow',\n label: 'Yellow Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-yellow-300 text-yellow-500'),\n isActive: editor.marks.textColor.isActive({ color: 'yellow' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'yellow' }),\n onClick: () => editor.commands.addTextColor({ color: 'yellow' }),\n },\n {\n key: 'green',\n label: 'Green Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-green-300 text-green-500'),\n isActive: editor.marks.textColor.isActive({ color: 'green' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'green' }),\n onClick: () => editor.commands.addTextColor({ color: 'green' }),\n },\n {\n key: 'blue',\n label: 'Blue Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-blue-300 text-blue-500'),\n isActive: editor.marks.textColor.isActive({ color: 'blue' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'blue' }),\n onClick: () => editor.commands.addTextColor({ color: 'blue' }),\n },\n {\n key: 'purple',\n label: 'Purple Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-purple-300 text-purple-500'),\n isActive: editor.marks.textColor.isActive({ color: 'purple' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'purple' }),\n onClick: () => editor.commands.addTextColor({ color: 'purple' }),\n },\n {\n key: 'pink',\n label: 'Pink Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-pink-300 text-pink-500'),\n isActive: editor.marks.textColor.isActive({ color: 'pink' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'pink' }),\n onClick: () => editor.commands.addTextColor({ color: 'pink' }),\n },\n {\n key: 'red',\n label: 'Red Text',\n iconClassName: clsx(TEXT_COLOR_CLASSNAME, 'border-red-300 text-red-500'),\n isActive: editor.marks.textColor.isActive({ color: 'red' }),\n isAvailable: editor.commands.addTextColor.canExec({ color: 'red' }),\n onClick: () => editor.commands.addTextColor({ color: 'red' }),\n },\n ],\n },\n {\n key: 'delete',\n label: 'Delete',\n iconClassName: 'i-lucide-trash-2',\n shortcut: 'Del',\n danger: true,\n isActive: false,\n isAvailable: true,\n onClick: () => editor.view.dispatch(editor.view.state.tr.deleteSelection()),\n },\n ]\n}\n\nfunction BlockHandleItem(props: { item: ItemInfo }) {\n if (!props.item.isAvailable) {\n return null\n } else if (props.item.children) {\n return (\n <Menu.SubmenuRoot>\n <Menu.SubmenuTrigger className={ITEM_CLASSNAME}>\n {props.item.iconClassName && <span className={clsx('inline-block size-4', props.item.iconClassName)} />}\n <span className=\"flex-1\">{props.item.label}</span>\n <span className=\"inline-block size-4 i-lucide-chevron-right opacity-50\">\n </span>\n </Menu.SubmenuTrigger>\n <Menu.Portal>\n <Menu.Positioner align=\"center\">\n <Menu.Popup className={POPUP_CLASSNAME}>\n {props.item.children.map(item => <BlockHandleItem key={item.key} item={item} />)}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.SubmenuRoot>\n )\n } else {\n return (\n <Menu.Item\n className={clsx(ITEM_CLASSNAME, 'group')}\n onClick={props.item.onClick}\n >\n {props.item.iconClassName && <span className={clsx('inline-block size-5', props.item.iconClassName)} />}\n <span className={clsx('flex-1', props.item.danger && 'group-data-highlighted:text-red-500')}>{props.item.label}</span>\n {props.item.isActive && <span className=\"inline-block size-4 i-lucide-check\"></span>}\n {!props.item.isActive && props.item.shortcut && <span className=\"opacity-50\">{props.item.shortcut}</span>}\n </Menu.Item>\n )\n }\n}\n\nexport default function BlockHandleMenu(props: Props) {\n const [open, setOpen] = useState(false)\n\n const items = useEditorDerivedValue(getMenuItems)\n\n return (\n <Menu.Root\n open={open}\n onOpenChange={(open, details) => {\n // ignore the event to open the menu because by default Menu is opened\n // by a `mousedown` event but we only want to open the menu by a `click`\n // event.\n if (open && details.reason === 'trigger-press') {\n return\n }\n setOpen(open)\n }}\n >\n <Menu.Trigger\n render={props.children}\n nativeButton={false}\n onClick={(event) => {\n event.preventDefault()\n setOpen(open => !open)\n }}\n >\n </Menu.Trigger>\n <Menu.Portal>\n <Menu.Backdrop className=\"size-dvw flex fixed inset-0 opacity-0\" />\n <Menu.Positioner className=\"outline-none\" side=\"right\" align=\"center\">\n <Menu.Popup className={POPUP_CLASSNAME}>\n {items.map(item => <BlockHandleItem key={item.key} item={item} />)}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n </Menu.Root>\n )\n}\n"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "registry/src/react/examples/notion/block-handle.tsx",
|
|
33
33
|
"type": "registry:component",
|
|
34
34
|
"target": "components/editor/examples/notion/block-handle.tsx",
|
|
35
|
-
"content": "'use client'\nimport { Tooltip } from '@base-ui/react/tooltip'\nimport { BlockHandleAdd, BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from 'prosekit/react/block-handle'\n\nimport BlockHandleMenu from './block-handle-menu'\n\ninterface Props {\n enabled: boolean\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n if (!props.enabled) {\n return null\n }\n\n return (\n <Tooltip.Provider>\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\"\n >\n <BlockHandlePopup className=\"flex box-border motion-safe:duration-100 data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100\">\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to add below</span>\n </span>\n <span>\n <span>Option-click{' '}</span>\n <span className=\"opacity-50\">to add above</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleMenu>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandleMenu>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Drag{' '}</span>\n <span className=\"opacity-50\">to move</span>\n </span>\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to open menu</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n </Tooltip.Provider>\n )\n}\n"
|
|
35
|
+
"content": "'use client'\n\nimport { Tooltip } from '@base-ui/react/tooltip'\nimport { BlockHandleAdd, BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from 'prosekit/react/block-handle'\n\nimport BlockHandleMenu from './block-handle-menu'\n\ninterface Props {\n enabled: boolean\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n if (!props.enabled) {\n return null\n }\n\n return (\n <Tooltip.Provider>\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\"\n >\n <BlockHandlePopup className=\"flex box-border motion-safe:duration-100 data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100\">\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to add below</span>\n </span>\n <span>\n <span>Option-click{' '}</span>\n <span className=\"opacity-50\">to add above</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleMenu>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandleMenu>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Drag{' '}</span>\n <span className=\"opacity-50\">to move</span>\n </span>\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to open menu</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n </Tooltip.Provider>\n )\n}\n"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"path": "registry/src/react/examples/notion/editor.tsx",
|
|
@@ -50,19 +50,19 @@
|
|
|
50
50
|
"path": "registry/src/react/examples/notion/image-view/image-view-content.tsx",
|
|
51
51
|
"type": "registry:component",
|
|
52
52
|
"target": "components/editor/examples/notion/image-view/image-view-content.tsx",
|
|
53
|
-
"content": "'use client'\nimport { UploadTask } from 'prosekit/extensions/file'\nimport type { ImageAttrs } from 'prosekit/extensions/image'\nimport type { ReactNodeViewProps } from 'prosekit/react'\nimport { ResizableHandle, ResizableRoot } from 'prosekit/react/resizable'\nimport { useEffect, useState, type SyntheticEvent } from 'react'\n\nexport default function ImageViewContent(props: ReactNodeViewProps) {\n const attrs = props.node.attrs as ImageAttrs\n const url = attrs.src || ''\n const uploading = url.startsWith('blob:')\n\n const [aspectRatio, setAspectRatio] = useState<number | undefined>()\n const [error, setError] = useState<string | undefined>()\n const [progress, setProgress] = useState(0)\n\n useEffect(() => {\n if (!uploading) return\n\n const uploadTask = UploadTask.get<string>(url)\n if (!uploadTask) return\n\n let canceled = false\n\n uploadTask.finished.catch((error) => {\n if (canceled) return\n setError(String(error))\n })\n const unsubscribeProgress = uploadTask.subscribeProgress(({ loaded, total }) => {\n if (canceled) return\n setProgress(total ? loaded / total : 0)\n })\n\n return () => {\n canceled = true\n unsubscribeProgress()\n }\n }, [url, uploading])\n\n const handleImageLoad = (event: SyntheticEvent) => {\n const img = event.target as HTMLImageElement\n const { naturalWidth, naturalHeight } = img\n const ratio = naturalWidth / naturalHeight\n if (ratio && Number.isFinite(ratio)) {\n setAspectRatio(ratio)\n }\n if (naturalWidth && naturalHeight && (!attrs.width || !attrs.height)) {\n props.setAttrs({ width: naturalWidth, height: naturalHeight })\n }\n }\n\n return (\n <ResizableRoot\n width={attrs.width ?? undefined}\n height={attrs.height ?? undefined}\n aspectRatio={aspectRatio}\n onResizeEnd={(event) => props.setAttrs(event.detail)}\n data-selected={props.selected ? '' : undefined}\n className=\"relative flex items-center justify-center box-border overflow-hidden my-2 group max-h-150 max-w-full min-h-16 min-w-16 outline-2 outline-transparent data-selected:outline-blue-500 outline-solid\"\n >\n {url && !error && (\n <img\n src={url}\n onLoad={handleImageLoad}\n alt=\"upload preview\"\n className=\"h-full w-full max-w-full max-h-full object-contain\"\n />\n )}\n {uploading && !error && (\n <div className=\"absolute bottom-0 left-0 m-1 flex content-center items-center gap-2 rounded-sm bg-gray-800/60 p-1.5 text-xs text-white/80 transition\">\n <div className=\"i-lucide-loader-circle size-4 animate-spin block\"></div>\n <div>{Math.round(progress * 100)}%</div>\n </div>\n )}\n {error && (\n <div className=\"absolute bottom-0 left-0 right-0 top-0 flex flex-col items-center justify-center gap-4 bg-gray-200 p-2 text-sm dark:bg-gray-800 @container\">\n <div className=\"i-lucide-image-off size-8 block\"></div>\n <div className=\"hidden opacity-80 @xs:block\">\n Failed to upload image\n </div>\n </div>\n )}\n <ResizableHandle\n className=\"absolute bottom-0 right-0 rounded-sm m-1.5 p-1 transition bg-gray-900/30 active:bg-gray-800/60 hover:bg-gray-800/60 text-white/50 active:text-white/80 active:translate-x-0.5 active:translate-y-0.5 opacity-0 hover:opacity-100 group-hover:opacity-100 group-data-resizing:opacity-100\"\n position=\"bottom-right\"\n >\n <div className=\"i-lucide-arrow-down-right size-4 block\"></div>\n </ResizableHandle>\n </ResizableRoot>\n )\n}\n"
|
|
53
|
+
"content": "'use client'\n\nimport { UploadTask } from 'prosekit/extensions/file'\nimport type { ImageAttrs } from 'prosekit/extensions/image'\nimport type { ReactNodeViewProps } from 'prosekit/react'\nimport { ResizableHandle, ResizableRoot } from 'prosekit/react/resizable'\nimport { useEffect, useState, type SyntheticEvent } from 'react'\n\nexport default function ImageViewContent(props: ReactNodeViewProps) {\n const attrs = props.node.attrs as ImageAttrs\n const url = attrs.src || ''\n const uploading = url.startsWith('blob:')\n\n const [aspectRatio, setAspectRatio] = useState<number | undefined>()\n const [error, setError] = useState<string | undefined>()\n const [progress, setProgress] = useState(0)\n\n useEffect(() => {\n if (!uploading) return\n\n const uploadTask = UploadTask.get<string>(url)\n if (!uploadTask) return\n\n let canceled = false\n\n uploadTask.finished.catch((error) => {\n if (canceled) return\n setError(String(error))\n })\n const unsubscribeProgress = uploadTask.subscribeProgress(({ loaded, total }) => {\n if (canceled) return\n setProgress(total ? loaded / total : 0)\n })\n\n return () => {\n canceled = true\n unsubscribeProgress()\n }\n }, [url, uploading])\n\n const handleImageLoad = (event: SyntheticEvent) => {\n const img = event.target as HTMLImageElement\n const { naturalWidth, naturalHeight } = img\n const ratio = naturalWidth / naturalHeight\n if (ratio && Number.isFinite(ratio)) {\n setAspectRatio(ratio)\n }\n if (naturalWidth && naturalHeight && (!attrs.width || !attrs.height)) {\n props.setAttrs({ width: naturalWidth, height: naturalHeight })\n }\n }\n\n return (\n <ResizableRoot\n width={attrs.width ?? undefined}\n height={attrs.height ?? undefined}\n aspectRatio={aspectRatio}\n onResizeEnd={(event) => props.setAttrs(event.detail)}\n data-selected={props.selected ? '' : undefined}\n className=\"relative flex items-center justify-center box-border overflow-hidden my-2 group max-h-150 max-w-full min-h-16 min-w-16 outline-2 outline-transparent data-selected:outline-blue-500 outline-solid\"\n >\n {url && !error && (\n <img\n src={url}\n onLoad={handleImageLoad}\n alt=\"upload preview\"\n className=\"h-full w-full max-w-full max-h-full object-contain\"\n />\n )}\n {uploading && !error && (\n <div className=\"absolute bottom-0 left-0 m-1 flex content-center items-center gap-2 rounded-sm bg-gray-800/60 p-1.5 text-xs text-white/80 transition\">\n <div className=\"i-lucide-loader-circle size-4 animate-spin block\"></div>\n <div>{Math.round(progress * 100)}%</div>\n </div>\n )}\n {error && (\n <div className=\"absolute bottom-0 left-0 right-0 top-0 flex flex-col items-center justify-center gap-4 bg-gray-200 p-2 text-sm dark:bg-gray-800 @container\">\n <div className=\"i-lucide-image-off size-8 block\"></div>\n <div className=\"hidden opacity-80 @xs:block\">\n Failed to upload image\n </div>\n </div>\n )}\n <ResizableHandle\n className=\"absolute bottom-0 right-0 rounded-sm m-1.5 p-1 transition bg-gray-900/30 active:bg-gray-800/60 hover:bg-gray-800/60 text-white/50 active:text-white/80 active:translate-x-0.5 active:translate-y-0.5 opacity-0 hover:opacity-100 group-hover:opacity-100 group-data-resizing:opacity-100\"\n position=\"bottom-right\"\n >\n <div className=\"i-lucide-arrow-down-right size-4 block\"></div>\n </ResizableHandle>\n </ResizableRoot>\n )\n}\n"
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"path": "registry/src/react/examples/notion/image-view/image-view-placeholder.tsx",
|
|
57
57
|
"type": "registry:component",
|
|
58
58
|
"target": "components/editor/examples/notion/image-view/image-view-placeholder.tsx",
|
|
59
|
-
"content": "'use client'\nimport { useEditor } from 'prosekit/react'\n\nimport { sampleUploader } from '../../../sample/sample-uploader'\nimport type { EditorExtension } from '../extension'\n\ninterface Props {\n getPos: () => number | undefined\n selected: boolean\n}\n\nexport default function ImageViewPlaceholder(props: Props) {\n const editor = useEditor<EditorExtension>()\n\n const handleFileChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {\n const file = event.target.files?.[0]\n if (!file) return\n\n const pos = props.getPos()\n if (typeof pos !== 'number') return\n\n editor.commands.uploadImage({ file, uploader: sampleUploader, pos, replace: true })\n\n // Reset input so the same file can be selected again\n event.target.value = ''\n }\n\n return (\n <label\n className=\"flex w-full cursor-pointer items-center rounded-lg gap-3 px-4 py-3 transition-colors data-selected:outline-blue-500 outline-1 outline-transparent bg-gray-500/10 text-current/40 hover:bg-gray-500/20 hover:text-current/60\"\n aria-label=\"Add an image\"\n data-selected={props.selected ? '' : undefined}\n >\n <input\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={handleFileChange}\n />\n <span className=\"block i-lucide-image size-4\" />\n <span className=\"text-sm font-medium\">Add an image</span>\n </label>\n )\n}\n"
|
|
59
|
+
"content": "'use client'\n\nimport { useEditor } from 'prosekit/react'\n\nimport { sampleUploader } from '../../../sample/sample-uploader'\nimport type { EditorExtension } from '../extension'\n\ninterface Props {\n getPos: () => number | undefined\n selected: boolean\n}\n\nexport default function ImageViewPlaceholder(props: Props) {\n const editor = useEditor<EditorExtension>()\n\n const handleFileChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {\n const file = event.target.files?.[0]\n if (!file) return\n\n const pos = props.getPos()\n if (typeof pos !== 'number') return\n\n editor.commands.uploadImage({ file, uploader: sampleUploader, pos, replace: true })\n\n // Reset input so the same file can be selected again\n event.target.value = ''\n }\n\n return (\n <label\n className=\"flex w-full cursor-pointer items-center rounded-lg gap-3 px-4 py-3 transition-colors data-selected:outline-blue-500 outline-1 outline-transparent bg-gray-500/10 text-current/40 hover:bg-gray-500/20 hover:text-current/60\"\n aria-label=\"Add an image\"\n data-selected={props.selected ? '' : undefined}\n >\n <input\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={handleFileChange}\n />\n <span className=\"block i-lucide-image size-4\" />\n <span className=\"text-sm font-medium\">Add an image</span>\n </label>\n )\n}\n"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"path": "registry/src/react/examples/notion/image-view/image-view.tsx",
|
|
63
63
|
"type": "registry:component",
|
|
64
64
|
"target": "components/editor/examples/notion/image-view/image-view.tsx",
|
|
65
|
-
"content": "'use client'\nimport type { ImageAttrs } from 'prosekit/extensions/image'\nimport type { ReactNodeViewProps } from 'prosekit/react'\n\nimport ImageViewContent from './image-view-content'\nimport ImageViewPlaceholder from './image-view-placeholder'\n\nexport default function ImageView(props: ReactNodeViewProps) {\n const attrs = props.node.attrs as ImageAttrs\n const url = attrs.src || ''\n\n if (url) {\n return <ImageViewContent {...props} />\n } else {\n return <ImageViewPlaceholder getPos={props.getPos} selected={props.selected} />\n }\n}\n"
|
|
65
|
+
"content": "'use client'\n\nimport type { ImageAttrs } from 'prosekit/extensions/image'\nimport type { ReactNodeViewProps } from 'prosekit/react'\n\nimport ImageViewContent from './image-view-content'\nimport ImageViewPlaceholder from './image-view-placeholder'\n\nexport default function ImageView(props: ReactNodeViewProps) {\n const attrs = props.node.attrs as ImageAttrs\n const url = attrs.src || ''\n\n if (url) {\n return <ImageViewContent {...props} />\n } else {\n return <ImageViewPlaceholder getPos={props.getPos} selected={props.selected} />\n }\n}\n"
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"path": "registry/src/react/examples/notion/image-view/index.ts",
|
|
@@ -86,19 +86,19 @@
|
|
|
86
86
|
"path": "registry/src/react/examples/notion/slash-menu/slash-menu-empty.tsx",
|
|
87
87
|
"type": "registry:component",
|
|
88
88
|
"target": "components/editor/examples/notion/slash-menu/slash-menu-empty.tsx",
|
|
89
|
-
"content": "'use client'\nimport { AutocompleteEmpty } from 'prosekit/react/autocomplete'\n\nexport default function SlashMenuEmpty() {\n return (\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n <span>No results</span>\n </AutocompleteEmpty>\n )\n}\n"
|
|
89
|
+
"content": "'use client'\n\nimport { AutocompleteEmpty } from 'prosekit/react/autocomplete'\n\nexport default function SlashMenuEmpty() {\n return (\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n <span>No results</span>\n </AutocompleteEmpty>\n )\n}\n"
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"path": "registry/src/react/examples/notion/slash-menu/slash-menu-item.tsx",
|
|
93
93
|
"type": "registry:component",
|
|
94
94
|
"target": "components/editor/examples/notion/slash-menu/slash-menu-item.tsx",
|
|
95
|
-
"content": "'use client'\nimport { AutocompleteItem } from 'prosekit/react/autocomplete'\n\nexport default function SlashMenuItem(props: {\n label: string\n kbd?: string\n onSelect: () => void\n}) {\n return (\n <AutocompleteItem onSelect={props.onSelect} className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n <span>{props.label}</span>\n {props.kbd && <kbd className=\"text-xs font-mono text-gray-400 dark:text-gray-500\">{props.kbd}</kbd>}\n </AutocompleteItem>\n )\n}\n"
|
|
95
|
+
"content": "'use client'\n\nimport { AutocompleteItem } from 'prosekit/react/autocomplete'\n\nexport default function SlashMenuItem(props: {\n label: string\n kbd?: string\n onSelect: () => void\n}) {\n return (\n <AutocompleteItem onSelect={props.onSelect} className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n <span>{props.label}</span>\n {props.kbd && <kbd className=\"text-xs font-mono text-gray-400 dark:text-gray-500\">{props.kbd}</kbd>}\n </AutocompleteItem>\n )\n}\n"
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
"path": "registry/src/react/examples/notion/slash-menu/slash-menu.tsx",
|
|
99
99
|
"type": "registry:component",
|
|
100
100
|
"target": "components/editor/examples/notion/slash-menu/slash-menu.tsx",
|
|
101
|
-
"content": "'use client'\nimport type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/react'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/react/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\ninterface Props {\n onOpenChange: (open: boolean) => void\n}\n\nexport default function SlashMenu(props: Props) {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot\n regex={regex}\n onOpenChange={(event) => {\n props.onOpenChange(event.detail)\n }}\n >\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\">\n <AutocompletePopup className=\"box-border data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100 motion-safe:duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuItem\n label=\"Image\"\n onSelect={() => editor.commands.insertImage({ src: '' })}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
|
|
101
|
+
"content": "'use client'\n\nimport type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/react'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/react/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\ninterface Props {\n onOpenChange: (open: boolean) => void\n}\n\nexport default function SlashMenu(props: Props) {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot\n regex={regex}\n onOpenChange={(event) => {\n props.onOpenChange(event.detail)\n }}\n >\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\">\n <AutocompletePopup className=\"box-border data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100 motion-safe:duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuItem\n label=\"Image\"\n onSelect={() => editor.commands.insertImage({ src: '' })}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"path": "registry/src/react/examples/notion/style.css",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"https://unpkg.com/prosekit-registry/dist/r/react-sample-sample-doc-page.json"
|
|
8
8
|
],
|
|
9
9
|
"dependencies": [
|
|
10
|
-
"prosekit@^0.20.0-beta.
|
|
10
|
+
"prosekit@^0.20.0-beta.5"
|
|
11
11
|
],
|
|
12
12
|
"files": [
|
|
13
13
|
{
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"path": "registry/src/react/examples/page/paper-controller.tsx",
|
|
33
33
|
"type": "registry:component",
|
|
34
34
|
"target": "components/editor/examples/page/paper-controller.tsx",
|
|
35
|
-
"content": "'use client'\nimport { definePageRendering, type PageRenderingOptions } from 'prosekit/extensions/page'\nimport { useExtension } from 'prosekit/react'\nimport { useEffect, useId, useMemo, useState } from 'react'\n\n// Paper sizes in pixels at 96 DPI\nconst PAPER_SIZES = {\n A3: { short: 1123, long: 1587 },\n A4: { short: 794, long: 1123 },\n A5: { short: 559, long: 794 },\n B4: { short: 945, long: 1334 },\n B5: { short: 665, long: 945 },\n letter: { short: 816, long: 1056 },\n} as const\n\ntype PaperSize = keyof typeof PAPER_SIZES\ntype Orientation = 'portrait' | 'landscape'\n\nexport default function PaperController({\n zoom,\n setZoom,\n}: {\n zoom: number\n setZoom: (zoom: number) => void\n}) {\n const id = useId()\n const [paperSize, setPaperSize] = useState<PaperSize>('A4')\n const [orientation, setOrientation] = useState<Orientation>('portrait')\n const [margin, setMargin] = useState(70)\n const [enablePageLayout, setEnablePageLayout] = useState(true)\n\n const pageRenderingOptions: PageRenderingOptions = useMemo(() => {\n const { short, long } = PAPER_SIZES[paperSize]\n const pageWidth = orientation === 'portrait' ? short : long\n const pageHeight = orientation === 'portrait' ? long : short\n\n return {\n pageWidth,\n pageHeight,\n marginTop: margin,\n marginRight: margin,\n marginBottom: margin,\n marginLeft: margin,\n }\n }, [paperSize, orientation, margin])\n\n useEffect(() => {\n const styleId = 'print-page-style'\n let style = document.getElementById(styleId) as HTMLStyleElement | null\n if (!style) {\n style = document.createElement('style')\n style.id = styleId\n document.head.appendChild(style)\n }\n style.textContent = `@page { size: ${paperSize} ${orientation}; margin: 0; }`\n\n return () => {\n style.textContent = ''\n }\n }, [paperSize, orientation])\n\n const extension = useMemo(() => {\n return enablePageLayout ? definePageRendering(pageRenderingOptions) : null\n }, [pageRenderingOptions, enablePageLayout])\n\n useExtension(extension)\n\n return (\n <div\n data-paper-controller={paperSize}\n className=\"grid grid-cols-[auto_1fr] gap-2 w-min border p-2 bg-[Canvas] sticky top-2 left-2 z-10 print:hidden text-xs\"\n >\n <label htmlFor={`${id}-page`}>Page</label>\n <select\n id={`${id}-page`}\n value={enablePageLayout ? 'Enabled' : 'Disabled'}\n onChange={(e) => setEnablePageLayout(e.target.value === 'Enabled')}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"Enabled\">Enabled</option>\n <option value=\"Disabled\">Disabled</option>\n </select>\n <label htmlFor={`${id}-paper`}>Paper Size</label>\n <select\n id={`${id}-paper`}\n value={paperSize}\n onChange={(e) => setPaperSize(e.target.value as PaperSize)}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"A3\">A3</option>\n <option value=\"A4\">A4</option>\n <option value=\"A5\">A5</option>\n <option value=\"B4\">B4</option>\n <option value=\"B5\">B5</option>\n <option value=\"letter\">Letter</option>\n </select>\n <label htmlFor={`${id}-orientation`}>Orientation</label>\n <select\n id={`${id}-orientation`}\n value={orientation}\n onChange={(e) => setOrientation(e.target.value as Orientation)}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"portrait\">Portrait</option>\n <option value=\"landscape\">Landscape</option>\n </select>\n <label htmlFor={`${id}-margin`}>Margin</label>\n <select\n id={`${id}-margin`}\n value={String(margin)}\n onChange={(e) => setMargin(Number.parseInt(e.target.value, 10))}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"30\">Narrow</option>\n <option value=\"70\">Normal</option>\n <option value=\"120\">Wide</option>\n </select>\n <label htmlFor={`${id}-zoom`}>Zoom</label>\n <select\n id={`${id}-zoom`}\n value={String(zoom)}\n onChange={(e) => setZoom(Number.parseInt(e.target.value, 10))}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"25\">25%</option>\n <option value=\"50\">50%</option>\n <option value=\"75\">75%</option>\n <option value=\"100\">100%</option>\n <option value=\"125\">125%</option>\n <option value=\"150\">150%</option>\n <option value=\"200\">200%</option>\n </select>\n </div>\n )\n}\n"
|
|
35
|
+
"content": "'use client'\n\nimport { definePageRendering, type PageRenderingOptions } from 'prosekit/extensions/page'\nimport { useExtension } from 'prosekit/react'\nimport { useEffect, useId, useMemo, useState } from 'react'\n\n// Paper sizes in pixels at 96 DPI\nconst PAPER_SIZES = {\n A3: { short: 1123, long: 1587 },\n A4: { short: 794, long: 1123 },\n A5: { short: 559, long: 794 },\n B4: { short: 945, long: 1334 },\n B5: { short: 665, long: 945 },\n letter: { short: 816, long: 1056 },\n} as const\n\ntype PaperSize = keyof typeof PAPER_SIZES\ntype Orientation = 'portrait' | 'landscape'\n\nexport default function PaperController({\n zoom,\n setZoom,\n}: {\n zoom: number\n setZoom: (zoom: number) => void\n}) {\n const id = useId()\n const [paperSize, setPaperSize] = useState<PaperSize>('A4')\n const [orientation, setOrientation] = useState<Orientation>('portrait')\n const [margin, setMargin] = useState(70)\n const [enablePageLayout, setEnablePageLayout] = useState(true)\n\n const pageRenderingOptions: PageRenderingOptions = useMemo(() => {\n const { short, long } = PAPER_SIZES[paperSize]\n const pageWidth = orientation === 'portrait' ? short : long\n const pageHeight = orientation === 'portrait' ? long : short\n\n return {\n pageWidth,\n pageHeight,\n marginTop: margin,\n marginRight: margin,\n marginBottom: margin,\n marginLeft: margin,\n }\n }, [paperSize, orientation, margin])\n\n useEffect(() => {\n const styleId = 'print-page-style'\n let style = document.getElementById(styleId) as HTMLStyleElement | null\n if (!style) {\n style = document.createElement('style')\n style.id = styleId\n document.head.appendChild(style)\n }\n style.textContent = `@page { size: ${paperSize} ${orientation}; margin: 0; }`\n\n return () => {\n style.textContent = ''\n }\n }, [paperSize, orientation])\n\n const extension = useMemo(() => {\n return enablePageLayout ? definePageRendering(pageRenderingOptions) : null\n }, [pageRenderingOptions, enablePageLayout])\n\n useExtension(extension)\n\n return (\n <div\n data-paper-controller={paperSize}\n className=\"grid grid-cols-[auto_1fr] gap-2 w-min border p-2 bg-[Canvas] sticky top-2 left-2 z-10 print:hidden text-xs\"\n >\n <label htmlFor={`${id}-page`}>Page</label>\n <select\n id={`${id}-page`}\n value={enablePageLayout ? 'Enabled' : 'Disabled'}\n onChange={(e) => setEnablePageLayout(e.target.value === 'Enabled')}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"Enabled\">Enabled</option>\n <option value=\"Disabled\">Disabled</option>\n </select>\n <label htmlFor={`${id}-paper`}>Paper Size</label>\n <select\n id={`${id}-paper`}\n value={paperSize}\n onChange={(e) => setPaperSize(e.target.value as PaperSize)}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"A3\">A3</option>\n <option value=\"A4\">A4</option>\n <option value=\"A5\">A5</option>\n <option value=\"B4\">B4</option>\n <option value=\"B5\">B5</option>\n <option value=\"letter\">Letter</option>\n </select>\n <label htmlFor={`${id}-orientation`}>Orientation</label>\n <select\n id={`${id}-orientation`}\n value={orientation}\n onChange={(e) => setOrientation(e.target.value as Orientation)}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"portrait\">Portrait</option>\n <option value=\"landscape\">Landscape</option>\n </select>\n <label htmlFor={`${id}-margin`}>Margin</label>\n <select\n id={`${id}-margin`}\n value={String(margin)}\n onChange={(e) => setMargin(Number.parseInt(e.target.value, 10))}\n disabled={!enablePageLayout}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"30\">Narrow</option>\n <option value=\"70\">Normal</option>\n <option value=\"120\">Wide</option>\n </select>\n <label htmlFor={`${id}-zoom`}>Zoom</label>\n <select\n id={`${id}-zoom`}\n value={String(zoom)}\n onChange={(e) => setZoom(Number.parseInt(e.target.value, 10))}\n className=\"rounded border disabled:opacity-50\"\n >\n <option value=\"25\">25%</option>\n <option value=\"50\">50%</option>\n <option value=\"75\">75%</option>\n <option value=\"100\">100%</option>\n <option value=\"125\">125%</option>\n <option value=\"150\">150%</option>\n <option value=\"200\">200%</option>\n </select>\n </div>\n )\n}\n"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"path": "registry/src/react/examples/page/zoom.css",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"https://unpkg.com/prosekit-registry/dist/r/react-ui-button.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [
|
|
11
|
-
"prosekit@^0.20.0-beta.
|
|
11
|
+
"prosekit@^0.20.0-beta.5"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"path": "registry/src/react/examples/readonly/toolbar.tsx",
|
|
34
34
|
"type": "registry:component",
|
|
35
35
|
"target": "components/editor/examples/readonly/toolbar.tsx",
|
|
36
|
-
"content": "'use client'\nimport { Button } from '../../ui/button'\n\nimport { useReadonly } from './use-readonly'\n\nexport default function Toolbar() {\n const { readonly, setReadonly } = useReadonly()\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button pressed={readonly} onClick={() => setReadonly(true)}>\n Readonly\n </Button>\n\n <Button pressed={!readonly} onClick={() => setReadonly(false)}>\n Editable\n </Button>\n </div>\n )\n}\n"
|
|
36
|
+
"content": "'use client'\n\nimport { Button } from '../../ui/button'\n\nimport { useReadonly } from './use-readonly'\n\nexport default function Toolbar() {\n const { readonly, setReadonly } = useReadonly()\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button pressed={readonly} onClick={() => setReadonly(true)}>\n Readonly\n </Button>\n\n <Button pressed={!readonly} onClick={() => setReadonly(false)}>\n Editable\n </Button>\n </div>\n )\n}\n"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"path": "registry/src/react/examples/readonly/use-readonly.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"https://unpkg.com/prosekit-registry/dist/r/react-ui-button.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [
|
|
11
|
-
"prosekit@^0.20.0-beta.
|
|
11
|
+
"prosekit@^0.20.0-beta.5"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"path": "registry/src/react/examples/strike/toolbar.tsx",
|
|
34
34
|
"type": "registry:component",
|
|
35
35
|
"target": "components/editor/examples/strike/toolbar.tsx",
|
|
36
|
-
"content": "'use client'\nimport { useEditor } from 'prosekit/react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nexport default function Toolbar() {\n const editor = useEditor<EditorExtension>({ update: true })\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={false}\n disabled={!editor.commands.toggleStrike.canExec()}\n onClick={() => editor.commands.toggleStrike()}\n >\n Strikethrough\n </Button>\n </div>\n )\n}\n"
|
|
36
|
+
"content": "'use client'\n\nimport { useEditor } from 'prosekit/react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nexport default function Toolbar() {\n const editor = useEditor<EditorExtension>({ update: true })\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={false}\n disabled={!editor.commands.toggleStrike.canExec()}\n onClick={() => editor.commands.toggleStrike()}\n >\n Strikethrough\n </Button>\n </div>\n )\n}\n"
|
|
37
37
|
}
|
|
38
38
|
],
|
|
39
39
|
"meta": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"https://unpkg.com/prosekit-registry/dist/r/react-ui-button.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [
|
|
11
|
-
"prosekit@^0.20.0-beta.
|
|
11
|
+
"prosekit@^0.20.0-beta.5"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"path": "registry/src/react/examples/text-align/toolbar.tsx",
|
|
34
34
|
"type": "registry:component",
|
|
35
35
|
"target": "components/editor/examples/text-align/toolbar.tsx",
|
|
36
|
-
"content": "'use client'\nimport type { Editor } from 'prosekit/core'\nimport { useEditorDerivedValue } from 'prosekit/react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nfunction isTextAlignActive(editor: Editor<EditorExtension>, value: string) {\n return Object.values(editor.nodes).some((node) => {\n // @ts-expect-error textAlign may not be available on every node\n return node.isActive({ textAlign: value })\n })\n}\n\nfunction getToolbarItems(editor: Editor<EditorExtension>) {\n return {\n left: {\n isActive: isTextAlignActive(editor, 'left'),\n canExec: editor.commands.setTextAlign.canExec('left'),\n command: () => editor.commands.setTextAlign('left'),\n },\n center: {\n isActive: isTextAlignActive(editor, 'center'),\n canExec: editor.commands.setTextAlign.canExec('center'),\n command: () => editor.commands.setTextAlign('center'),\n },\n right: {\n isActive: isTextAlignActive(editor, 'right'),\n canExec: editor.commands.setTextAlign.canExec('right'),\n command: () => editor.commands.setTextAlign('right'),\n },\n justify: {\n isActive: isTextAlignActive(editor, 'justify'),\n canExec: editor.commands.setTextAlign.canExec('justify'),\n command: () => editor.commands.setTextAlign('justify'),\n },\n }\n}\n\nexport default function Toolbar() {\n const items = useEditorDerivedValue(getToolbarItems)\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={items.left.isActive}\n disabled={!items.left.canExec}\n onClick={items.left.command}\n >\n Left\n </Button>\n\n <Button\n pressed={items.center.isActive}\n disabled={!items.center.canExec}\n onClick={items.center.command}\n >\n Center\n </Button>\n\n <Button\n pressed={items.right.isActive}\n disabled={!items.right.canExec}\n onClick={items.right.command}\n >\n Right\n </Button>\n\n <Button\n pressed={items.justify.isActive}\n disabled={!items.justify.canExec}\n onClick={items.justify.command}\n >\n Justify\n </Button>\n </div>\n )\n}\n"
|
|
36
|
+
"content": "'use client'\n\nimport type { Editor } from 'prosekit/core'\nimport { useEditorDerivedValue } from 'prosekit/react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nfunction isTextAlignActive(editor: Editor<EditorExtension>, value: string) {\n return Object.values(editor.nodes).some((node) => {\n // @ts-expect-error textAlign may not be available on every node\n return node.isActive({ textAlign: value })\n })\n}\n\nfunction getToolbarItems(editor: Editor<EditorExtension>) {\n return {\n left: {\n isActive: isTextAlignActive(editor, 'left'),\n canExec: editor.commands.setTextAlign.canExec('left'),\n command: () => editor.commands.setTextAlign('left'),\n },\n center: {\n isActive: isTextAlignActive(editor, 'center'),\n canExec: editor.commands.setTextAlign.canExec('center'),\n command: () => editor.commands.setTextAlign('center'),\n },\n right: {\n isActive: isTextAlignActive(editor, 'right'),\n canExec: editor.commands.setTextAlign.canExec('right'),\n command: () => editor.commands.setTextAlign('right'),\n },\n justify: {\n isActive: isTextAlignActive(editor, 'justify'),\n canExec: editor.commands.setTextAlign.canExec('justify'),\n command: () => editor.commands.setTextAlign('justify'),\n },\n }\n}\n\nexport default function Toolbar() {\n const items = useEditorDerivedValue(getToolbarItems)\n\n return (\n <div className=\"z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center\">\n <Button\n pressed={items.left.isActive}\n disabled={!items.left.canExec}\n onClick={items.left.command}\n >\n Left\n </Button>\n\n <Button\n pressed={items.center.isActive}\n disabled={!items.center.canExec}\n onClick={items.center.command}\n >\n Center\n </Button>\n\n <Button\n pressed={items.right.isActive}\n disabled={!items.right.canExec}\n onClick={items.right.command}\n >\n Right\n </Button>\n\n <Button\n pressed={items.justify.isActive}\n disabled={!items.justify.canExec}\n onClick={items.justify.command}\n >\n Justify\n </Button>\n </div>\n )\n}\n"
|
|
37
37
|
}
|
|
38
38
|
],
|
|
39
39
|
"meta": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"https://unpkg.com/prosekit-registry/dist/r/react-ui-button.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [
|
|
11
|
-
"prosekit@^0.20.0-beta.
|
|
11
|
+
"prosekit@^0.20.0-beta.5"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"path": "registry/src/react/examples/text-color/inline-menu.tsx",
|
|
34
34
|
"type": "registry:component",
|
|
35
35
|
"target": "components/editor/examples/text-color/inline-menu.tsx",
|
|
36
|
-
"content": "'use client'\nimport type { Editor, Keymap } from 'prosekit/core'\nimport { useEditorDerivedValue, useKeymap } from 'prosekit/react'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/react/inline-popover'\nimport { useMemo, useState } from 'react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nconst textColors = [\n { label: 'Gray', value: '#9ca3af' },\n { label: 'Brown', value: '#92400e' },\n { label: 'Orange', value: '#ea580c' },\n { label: 'Yellow', value: '#ca8a04' },\n { label: 'Green', value: '#16a34a' },\n { label: 'Blue', value: '#2563eb' },\n { label: 'Purple', value: '#9333ea' },\n { label: 'Magenta', value: '#c026d3' },\n { label: 'Red', value: '#dc2626' },\n]\n\nconst backgroundColors = [\n { label: 'Gray', value: '#f3f4f6' },\n { label: 'Brown', value: '#fef3c7' },\n { label: 'Orange', value: '#ffedd5' },\n { label: 'Yellow', value: '#fef9c3' },\n { label: 'Green', value: '#d1fae5' },\n { label: 'Blue', value: '#dbeafe' },\n { label: 'Purple', value: '#e9d5ff' },\n { label: 'Pink', value: '#fce7f3' },\n { label: 'Red', value: '#fecaca' },\n]\n\nfunction getTextColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'currentColor',\n isActive: !editor.marks.textColor.isActive(),\n onClick: () => editor.commands.removeTextColor(),\n }].concat(textColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.textColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addTextColor({ color: color.value }),\n })))\n}\n\nfunction getBackgroundColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'canvas',\n isActive: !editor.marks.backgroundColor.isActive(),\n onClick: () => editor.commands.removeBackgroundColor(),\n }].concat(\n backgroundColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.backgroundColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addBackgroundColor({ color: color.value }),\n })),\n )\n}\n\nexport default function InlineMenu() {\n const textColorState = useEditorDerivedValue(getTextColorState)\n const backgroundColorState = useEditorDerivedValue(getBackgroundColorState)\n const [open, setOpen] = useState(false)\n\n const keymap: Keymap = useMemo(() => ({\n Escape: () => {\n if (open) {\n setOpen(false)\n return true\n }\n return false\n },\n }), [open])\n\n useKeymap(keymap)\n\n return (\n <InlinePopoverRoot\n open={open}\n onOpenChange={(event) => setOpen(event.detail)}\n >\n <InlinePopoverPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\">\n <InlinePopoverPopup className=\"box-border data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100 motion-safe:duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\">\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Text color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {textColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Text: ${color.label}`}\n onClick={color.onClick}\n >\n <span\n className=\"text-base font-medium\"\n style={{ color: color.value }}\n >\n A\n </span>\n </Button>\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Background color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {backgroundColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Background: ${color.label}`}\n onClick={color.onClick}\n >\n <div\n className=\"w-6 h-6 rounded border border-gray-200 dark:border-gray-700\"\n style={{ backgroundColor: color.value }}\n />\n </Button>\n ))}\n </div>\n </div>\n </div>\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )\n}\n"
|
|
36
|
+
"content": "'use client'\n\nimport type { Editor, Keymap } from 'prosekit/core'\nimport { useEditorDerivedValue, useKeymap } from 'prosekit/react'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/react/inline-popover'\nimport { useMemo, useState } from 'react'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nconst textColors = [\n { label: 'Gray', value: '#9ca3af' },\n { label: 'Brown', value: '#92400e' },\n { label: 'Orange', value: '#ea580c' },\n { label: 'Yellow', value: '#ca8a04' },\n { label: 'Green', value: '#16a34a' },\n { label: 'Blue', value: '#2563eb' },\n { label: 'Purple', value: '#9333ea' },\n { label: 'Magenta', value: '#c026d3' },\n { label: 'Red', value: '#dc2626' },\n]\n\nconst backgroundColors = [\n { label: 'Gray', value: '#f3f4f6' },\n { label: 'Brown', value: '#fef3c7' },\n { label: 'Orange', value: '#ffedd5' },\n { label: 'Yellow', value: '#fef9c3' },\n { label: 'Green', value: '#d1fae5' },\n { label: 'Blue', value: '#dbeafe' },\n { label: 'Purple', value: '#e9d5ff' },\n { label: 'Pink', value: '#fce7f3' },\n { label: 'Red', value: '#fecaca' },\n]\n\nfunction getTextColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'currentColor',\n isActive: !editor.marks.textColor.isActive(),\n onClick: () => editor.commands.removeTextColor(),\n }].concat(textColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.textColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addTextColor({ color: color.value }),\n })))\n}\n\nfunction getBackgroundColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'canvas',\n isActive: !editor.marks.backgroundColor.isActive(),\n onClick: () => editor.commands.removeBackgroundColor(),\n }].concat(\n backgroundColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.backgroundColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addBackgroundColor({ color: color.value }),\n })),\n )\n}\n\nexport default function InlineMenu() {\n const textColorState = useEditorDerivedValue(getTextColorState)\n const backgroundColorState = useEditorDerivedValue(getBackgroundColorState)\n const [open, setOpen] = useState(false)\n\n const keymap: Keymap = useMemo(() => ({\n Escape: () => {\n if (open) {\n setOpen(false)\n return true\n }\n return false\n },\n }), [open])\n\n useKeymap(keymap)\n\n return (\n <InlinePopoverRoot\n open={open}\n onOpenChange={(event) => setOpen(event.detail)}\n >\n <InlinePopoverPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 motion-safe:ease-out motion-safe:transition-transform motion-safe:duration-100\">\n <InlinePopoverPopup className=\"box-border data-[state=closed]:motion-safe:duration-150 motion-safe:transition-discrete motion-safe:transition-all data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100 motion-safe:duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\">\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Text color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {textColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Text: ${color.label}`}\n onClick={color.onClick}\n >\n <span\n className=\"text-base font-medium\"\n style={{ color: color.value }}\n >\n A\n </span>\n </Button>\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Background color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {backgroundColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Background: ${color.label}`}\n onClick={color.onClick}\n >\n <div\n className=\"w-6 h-6 rounded border border-gray-200 dark:border-gray-700\"\n style={{ backgroundColor: color.value }}\n />\n </Button>\n ))}\n </div>\n </div>\n </div>\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )\n}\n"
|
|
37
37
|
}
|
|
38
38
|
],
|
|
39
39
|
"meta": {
|