From 489980301b40d950d43870acbb979cfe62cf61a3 Mon Sep 17 00:00:00 2001 From: Shevchik Igor Date: Fri, 10 Jul 2026 18:54:10 +0000 Subject: [PATCH] feat(Editor): allow disabling starter kit for plain text (76d613c) The starterKit prop now accepts boolean | Partial (default true). Passing false yields a plain-text editor: all formatting extensions are disabled while the essential nodes remain, and the Code / HorizontalRule extend blocks are gated behind starterKit !== false. b24ui's dropcursor color token is preserved. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb --- ...6d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md | 36 +++++++++++ .sync/nuxt-ui.json | 10 +++- docs/content/docs/2.components/editor.md | 4 ++ src/runtime/components/Editor.vue | 59 ++++++++++++++----- 4 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 .sync/log/76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md diff --git a/.sync/log/76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md b/.sync/log/76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md new file mode 100644 index 000000000..e613c901e --- /dev/null +++ b/.sync/log/76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md @@ -0,0 +1,36 @@ +# Port: feat(Editor): allow disabling starter kit for plain text (#6713) + +**Upstream:** `76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2` (nuxt/ui) +**Decision:** port — adapted (b24ui dropcursor token) + +## Upstream change +`Editor`'s `starterKit` prop now accepts `boolean | Partial` +(default `true`). Passing `false` gives a **plain-text** editor: the `starterKit` +computed builds a `plainText` override that disables every formatting extension +(bold/italic/heading/lists/code/blockquote/link/hr/underline/strike/…) while +keeping the essential nodes (document, paragraph, text, hard break, history), and +the `Code` / `HorizontalRule` extend blocks are gated behind +`props.starterKit !== false`. Docs gain a `::tip` explaining plain-text mode. + +## b24ui port +b24ui's `Editor.vue` matched upstream's structure. Applied verbatim: +- prop type → `boolean | Partial` + the "Set to `false`" jsdoc line; +- `withDefaults` → `starterKit: true`; +- the `starterKit` computed rewrite (boolean/plainText/defu); +- the `props.starterKit !== false &&` guards on `Code.extend` / `HorizontalRule.extend`. + +### Deviation — dropcursor color token +b24ui's dropcursor color is `var(--ui-color-accent-main-primary)` (fork token), +not upstream's `var(--ui-primary)`. Preserved b24ui's token in the rewritten +computed's `defu` defaults. (The prop jsdoc's `@defaultValue` still shows +`var(--ui-primary)` — a pre-existing b24ui doc/code mismatch, left untouched.) + +Docs: added the `::tip` before the StarterKit `::callout` in `editor.md`. + +## Tests +Default `starterKit: true` reproduces the previous config exactly (defu of `{}` +yields the same object; `!== false` guards stay truthy), so no behavior change and +no snapshot churn. Suite unchanged: 5477 passed / 6 skipped. + +## Verify (CI=true) +`dev:prepare` · `lint` · `typecheck` · `test` · `build` — all green. diff --git a/.sync/nuxt-ui.json b/.sync/nuxt-ui.json index d112e3190..73c4b1a32 100644 --- a/.sync/nuxt-ui.json +++ b/.sync/nuxt-ui.json @@ -2,7 +2,7 @@ "upstream": "nuxt/ui", "branch": "v4", "sync_enabled": false, - "cursor": "7e6d8b0e69c6de392c2df45008f4722c1eda740d", + "cursor": "76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2", "_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).", "stats": { "queue_depth": 0, @@ -1025,10 +1025,16 @@ "summary": "fix(types): type prose components in app config (#6711) — TVConfig now recurses for the prose key (P extends 'prose' ? TVConfig : {...}) so nested prose component configs are typed. Upstream edits both halves of its intersected mapped type; b24ui has a single merged mapped type (compoundVariants inline), so the fix collapses to one edit. Type-only. Tests 5477." }, "7e6d8b0e69c6de392c2df45008f4722c1eda740d": { + "pr": 270, + "b24ui_sha": "4a31c05cf360041aa8abda31cb34b474837c5238", + "decision": "port", + "summary": "fix(Editor): prevent suggestion menu blinking on keystroke (#6712) — tiptap suggestion plugin emits a loading pass (placeholder items) before resolved items; onStart/onUpdate acted on it, opening empty and tearing down/recreating the menu each keystroke. onStart: move filteredItems below a `if (suggestionProps.loading) return` guard; onUpdate: hoist commandFn to top then loading guard before re-filtering. b24ui handlers matched, applied 1:1. Type-only tiptap loading field typechecks. Tests 5477." + }, + "76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2": { "pr": null, "b24ui_sha": "pending-merge", "decision": "port", - "summary": "fix(Editor): prevent suggestion menu blinking on keystroke (#6712) — tiptap suggestion plugin emits a loading pass (placeholder items) before resolved items; onStart/onUpdate acted on it, opening empty and tearing down/recreating the menu each keystroke. onStart: move filteredItems below a `if (suggestionProps.loading) return` guard; onUpdate: hoist commandFn to top then loading guard before re-filtering. b24ui handlers matched, applied 1:1. Type-only tiptap loading field typechecks. Tests 5477." + "summary": "feat(Editor): allow disabling starter kit for plain text (#6713) — starterKit prop now boolean | Partial (default true); false gives plain-text editor (plainText override disables all formatting, keeps essential nodes), Code/HorizontalRule gated behind props.starterKit !== false. b24ui Editor.vue matched, applied verbatim; kept b24ui dropcursor token var(--ui-color-accent-main-primary). Docs: added ::tip in editor.md. Default true reproduces prior config -> no snapshot churn. Tests 5477." } } } diff --git a/docs/content/docs/2.components/editor.md b/docs/content/docs/2.components/editor.md index dc15fc50e..87922b9a5 100644 --- a/docs/content/docs/2.components/editor.md +++ b/docs/content/docs/2.components/editor.md @@ -242,6 +242,10 @@ const value = ref('

Hello World

\n') ``` +::tip +Set `starter-kit` to `false` for a plain text editor. It keeps the essential nodes (paragraph, text, history) and disables every formatting feature such as bold, italic, headings, lists, code, blockquote, links and horizontal rules. +:: + ::callout{to="https://tiptap.dev/docs/editor/extensions/functionality/starterkit" target="_blank"} Learn more about StarterKit extension in the TipTap documentation. :: diff --git a/src/runtime/components/Editor.vue b/src/runtime/components/Editor.vue index 3da3912aa..21d4ec2b2 100644 --- a/src/runtime/components/Editor.vue +++ b/src/runtime/components/Editor.vue @@ -29,10 +29,11 @@ export interface EditorProps + starterKit?: boolean | Partial /** * The placeholder text to show in empty paragraphs. Can be a string or PlaceholderOptions from `@tiptap/extension-placeholder`. * @defaultValue { showOnlyWhenEditable: false, showOnlyCurrent: true, mode: 'everyLine' } @@ -108,7 +109,8 @@ defineOptions({ inheritAttrs: false }) const _props = withDefaults(defineProps>(), { image: true, - mention: true + mention: true, + starterKit: true }) const emits = defineEmits>() @@ -139,17 +141,44 @@ const editorProps = computed(() => defu(props.editorProps, { // eslint-disable-next-line vue/no-dupe-keys const contentType = computed(() => props.contentType || (typeof props.modelValue === 'string' ? 'html' : 'json')) // eslint-disable-next-line vue/no-dupe-keys -const starterKit = computed(() => defu(props.starterKit, { - code: false, - horizontalRule: false, - dropcursor: { - color: 'var(--ui-color-accent-main-primary)', - width: 2 - }, - link: { - openOnClick: false - } -} as Partial)) +const starterKit = computed(() => { + const options: Partial = typeof props.starterKit === 'boolean' ? {} : (props.starterKit ?? {}) + + // When disabled, keep a plain-text editor: document, paragraph, text, hard break and history remain, all formatting is turned off + const plainText: Partial = props.starterKit === false + ? { + blockquote: false, + bold: false, + bulletList: false, + code: false, + codeBlock: false, + dropcursor: false, + gapcursor: false, + heading: false, + horizontalRule: false, + italic: false, + listItem: false, + listKeymap: false, + link: false, + orderedList: false, + strike: false, + underline: false, + trailingNode: false + } + : {} + + return defu(options, plainText, { + code: false, + horizontalRule: false, + dropcursor: { + color: 'var(--ui-color-accent-main-primary)', + width: 2 + }, + link: { + openOnClick: false + } + } as Partial) +}) // eslint-disable-next-line vue/no-dupe-keys const placeholder = computed(() => { const options = typeof props.placeholder === 'string' ? { placeholder: props.placeholder } : props.placeholder @@ -188,10 +217,10 @@ const mention = computed(() => defu(typeof props.mention === 'boolean' ? {} : pr const extensions = computed(() => [ contentType.value === 'markdown' && Markdown.configure(markdown.value), StarterKit.configure(starterKit.value), - Code.extend({ + props.starterKit !== false && Code.extend({ excludes: 'code' }), - HorizontalRule.extend({ + props.starterKit !== false && HorizontalRule.extend({ renderHTML() { return [ 'div',