Skip to content

fix(admin): make Buttons blocks editable in the rich text editor - #4595

Merged
swissky merged 5 commits into
emdash-cms:mainfrom
swissky:fix/buttons-block-editor
Nov 6, 2026
Merged

swissky merged 5 commits into
emdash-cms:mainfrom
swissky:fix/buttons-block-editor

Conversation

@swissky

@swissky swissky commented Nov 2, 2026

Copy link
Copy Markdown
Member

What does this PR do?

WordPress imports turn core/buttons groups into buttons blocks, and the site renders them, but the admin had no editor for them. Since #4314 the card says "No editor is available for this block type", so imported buttons could only be kept or deleted, and there was no way to add a new group.

This registers a built-in Buttons block in the Portable Text editor using the existing Block Kit form:

  • Layout select (horizontal / vertical)
  • Repeatable buttons with text, URL and style (default / outline / fill), reorderable by drag
  • /buttons in the slash menu inserts a new group

Saving writes the shape Buttons.astro / Button.astro already render (_type: "button" items with _key). Existing item keys and any extra item properties are kept, and nothing is written to the parent's id. If a plugin registers its own buttons block, the editor keeps using the plugin's definition.

Out of scope: an open-in-new-tab option. Button.astro has no target support yet, so a toggle would save data the site ignores.

Closes #4313

Type of change

  • Bug fix
  • Feature (requires an accepted feature plan or RFC)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Your connection to this change

  • I use EmDash and ran into the problem this PR addresses
  • I maintain a tool, dependency, or service that EmDash uses, and this change concerns that integration
  • A Maintainer approved this specific change before I opened the PR

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to their merged design PR: https://github.com/emdash-cms/emdash/pull/...
  • I have included screenshots below if this PR changes the UI

AI assistance disclosure

  • This PR includes AI-generated code or text — model/tool: Claude Opus 5.5

Screenshots / test output

Before: the imported group shows "No editor is available for this block type. Its content is preserved." with only a delete action.

After, the imported group in the editor:

Editor showing a Buttons card for an imported group with two buttons

After, editing the group: layout select and the list of buttons with text, URL and style fields:

Edit Buttons dialog with layout set to Horizontal and the first button expanded

New tests/editor/buttons-block-editor.test.tsx: editing an imported button and inserting via /buttons both fail on main and pass with this change. The fixture in unsupported-block.test.tsx moved from buttons to an unknown columns type so it still covers the no-editor fallback. Full admin suite: 284 files, 4137 tests passed.

@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/admin size/L labels Nov 2, 2026
@changeset-bot

changeset-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 813938b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-test Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
create-emdash Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering a built-in buttons definition through the existing Block Kit plugin-block path is the right approach. It lets imported groups open a real form and lets /buttons insert a group. It also defers to a plugin's own buttons definition, which fits the editor's architecture. The main issue is that _key values are reused by array position, so deleting or reordering buttons moves keys onto different buttons. This contradicts the claim that existing item keys are kept. Both reviewers raised it. Anthropic rated it a non-blocking suggestion and said it is harmless for Buttons.astro. OpenAI rated it needs_fixing because consumers that track keyed items could be misled. I kept the higher severity because the stated reasons (identity drift, no tests) support it. Separately, OpenAI found that the new test omits the .js import extensions required by the repo's ESM convention. Anthropic did not mention this. Neither reviewer ran tests.


Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.

return {
...rest,
_type: "button",
_key: previousKeys[index] || generateKey(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] This assigns an existing key by array position (previousKeys[index]), not by button identity. BlockKitRepeater strips _key whenever it emits after edits, deletions, and drag reordering. Example: deleting A from [A(key-a), B(key-b)] saves B with key-a. Reordering swaps the two keys. This contradicts the promised preservation of existing item keys. It can break anything that tracks Portable Text items by _key, such as revision diffs or custom renderers. Preserve the original Portable Text key with each row through repeater operations, separately from any UI-only key. Generate a new key only for genuinely new buttons. Add regression tests for deleting and reordering a multi-button group.

Reviewer disagreement: Anthropic rated this a non-blocking suggestion because Buttons.astro is unaffected. OpenAI rated it needs_fixing because of the key-identity risk. Anthropic also suggested narrowing the doc comment if positional reuse is kept deliberately.

Comment on lines +4 to +5
import { PortableTextEditor } from "../../src/components/PortableTextEditor";
import { render } from "../utils/render";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] These new relative imports omit the .js extensions required by the root AGENTS.md ESM import convention. The neighboring unsupported-block test already uses the required spelling.

Suggested change
import { PortableTextEditor } from "../../src/components/PortableTextEditor";
import { render } from "../utils/render";
import { PortableTextEditor } from "../../src/components/PortableTextEditor.js";
import { render } from "../utils/render.js";

@pkg-pr-new

pkg-pr-new Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@4595

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@4595

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@4595

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@4595

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@4595

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@4595

emdash

npm i https://pkg.pr.new/emdash@4595

create-emdash

npm i https://pkg.pr.new/create-emdash@4595

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@4595

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@4595

@emdash-cms/plugin-test

npm i https://pkg.pr.new/@emdash-cms/plugin-test@4595

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@4595

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@4595

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@4595

@emdash-cms/registry-loader

npm i https://pkg.pr.new/@emdash-cms/registry-loader@4595

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@4595

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@4595

upgrade-emdash

npm i https://pkg.pr.new/upgrade-emdash@4595

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@4595

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@4595

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@4595

@emdash-cms/plugin-ai-search

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-search@4595

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@4595

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@4595

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@4595

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@4595

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@4595

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@4595

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@4595

commit: 813938b

@swissky swissky added the bot:review Trigger an emdashbot code review on this PR label Nov 2, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering a built-in buttons definition through the existing Block Kit plugin-block path is the right approach. Imported WordPress button groups now open a real form, /buttons inserts a group, and a plugin's own buttons definition still takes precedence. Both reviewers approved with no findings. Both earlier findings are resolved at this head. Each button's _key is carried in _editorKey through edits, deletions and reordering, and the test imports now use the required .js extensions. A regression test covers deleting a button. The reviewers did not add a drag-reorder test, but they reasoned that reordering moves the same row objects, so the key stays with its button. The review was static; tests, builds and lint were not run.


Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Nov 2, 2026
@swissky swissky added the ci:run Approve and re-run CI on the PR's latest commit label Nov 2, 2026
@github-actions github-actions Bot removed the ci:run Approve and re-run CI on the PR's latest commit label Nov 2, 2026

@danielmlr danielmlr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Kevin, I found 3 things to sort out before merge:

  1. Groups saved with the old "Enter URL..." box from #4313 still have that URL as their id. The card keeps showing it, with Copy URL and Open in new tab, and every save writes it back. I think clearing it when the buttons block is saved would tidy those up. It probably needs id: "", since the edit path keeps the old id when none comes through.

  2. If buttons isn't a list, the form says the value stays as it is, but saving turns it into []. Returning it unchanged from normalizeButtonItems in that case would keep that promise.

  3. German already translates Outline as "Gliederung" for the document outline, so the style select would show that. A context on the style labels, like t({ message: "Outline", context: "button style" }), would keep them apart.

Small one: an item_label on the repeater would show "Add button" instead of "Add item".

@emdashbot emdashbot Bot added review/needs-review No maintainer or bot review yet and removed review/needs-review No maintainer or bot review yet labels Nov 3, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Nov 3, 2026
@github-actions github-actions Bot added review/needs-rereview New commits since the last review and removed review/approved Approved; no new commits since labels Nov 4, 2026
@swissky

swissky commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

Thanks @danielmlr , all four are in 935361f: saving a buttons group now writes id: "", a non-list buttons value is kept as is, the style labels have a button style context, and the repeater says "Add button". Each has a test that fails without the change

@swissky swissky added the bot:review Trigger an emdashbot code review on this PR label Nov 4, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering a built-in buttons definition through the existing Block Kit plugin-block path is the right approach. Imported WordPress button groups now open a real form, /buttons inserts a group, and a plugin's own buttons definition still takes precedence. Both reviewers approved with no findings. The earlier findings stay resolved at this head. Each button's key travels with its row through edits, deletions and reordering, and the test imports use .js extensions. This head also clears a stray group-level id on save, with tests and a changeset update. Both reviews were static; tests, builds and lint were not run.


Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Nov 4, 2026
@emdashbot emdashbot Bot removed the review/needs-rereview New commits since the last review label Nov 6, 2026
@emdashbot emdashbot Bot added the review/needs-rereview New commits since the last review label Nov 6, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview New commits since the last review labels Nov 6, 2026
@swissky
swissky merged commit 213904f into emdash-cms:main Nov 6, 2026
43 checks passed
khoinguyenpham04 added a commit that referenced this pull request Nov 6, 2026
Resolve the conflict with main's editable Buttons block (#4595) in
PortableTextEditor: keep this branch's key-based plugin block lookup and
main's button key and item handling, and restore the local isRecord the
button helpers use, since this branch moved the converter out of the
editor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WordPress-imported buttons blocks can't be edited in the admin: generic "Enter URL..." fallback saves a stray URL on the block

3 participants