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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .sync/log/76d613c590ed8f0ac7884aa3c3ca2cd5027da6e2.md
Original file line number Diff line number Diff line change
@@ -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<StarterKitOptions>`
(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<StarterKitOptions>` + 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.
10 changes: 8 additions & 2 deletions .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<T[P]> : {...}) 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<StarterKitOptions> (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."
}
}
}
4 changes: 4 additions & 0 deletions docs/content/docs/2.components/editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ const value = ref('<h1>Hello World</h1>\n')
</template>
```

::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.
::
Expand Down
59 changes: 44 additions & 15 deletions src/runtime/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export interface EditorProps<T extends Content = Content, H extends EditorCustom
contentType?: EditorContentType
/**
* The starter kit options to configure the editor.
* Set to `false` for a plain-text editor: keeps the essential nodes (paragraph, text, history) and disables all rich-text formatting.
* @defaultValue { horizontalRule: false, link: { openOnClick: false }, dropcursor: { color: 'var(--ui-primary)', width: 2 } }
* @see https://tiptap.dev/docs/editor/extensions/functionality/starterkit
*/
starterKit?: Partial<StarterKitOptions>
starterKit?: boolean | Partial<StarterKitOptions>
/**
* 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' }
Expand Down Expand Up @@ -108,7 +109,8 @@ defineOptions({ inheritAttrs: false })

const _props = withDefaults(defineProps<EditorProps<T, H>>(), {
image: true,
mention: true
mention: true,
starterKit: true
})
const emits = defineEmits<EditorEmits<T>>()

Expand Down Expand Up @@ -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<StarterKitOptions>))
const starterKit = computed(() => {
const options: Partial<StarterKitOptions> = 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<StarterKitOptions> = 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<StarterKitOptions>)
})
// eslint-disable-next-line vue/no-dupe-keys
const placeholder = computed(() => {
const options = typeof props.placeholder === 'string' ? { placeholder: props.placeholder } : props.placeholder
Expand Down Expand Up @@ -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',
Expand Down