Skip to content

feat(admin): add table support to PortableText editor#921

Merged
ascorbic merged 3 commits into
emdash-cms:mainfrom
jcheese1:codex-table-support
May 6, 2026
Merged

feat(admin): add table support to PortableText editor#921
ascorbic merged 3 commits into
emdash-cms:mainfrom
jcheese1:codex-table-support

Conversation

@jcheese1
Copy link
Copy Markdown
Contributor

@jcheese1 jcheese1 commented May 6, 2026

What does this PR do?

Adds table support to the admin PortableText editor, porting the abandoned work from #222 and adjusting mark definition storage so authored tables render with the existing table renderer.

Discussion: #217

Changes:

  • Adds TipTap table extensions to the admin editor.
  • Adds a /table slash-command entry and toolbar button.
  • Adds a table bubble menu for adding/removing rows and columns, toggling the header row, and deleting the table.
  • Serializes editor tables to PortableText _type: "table" blocks.
  • Stores table-cell markDefs on the cell payload so links render through the existing @emdash-cms/blocks table renderer.
  • Adds browser component tests for table insertion, editing controls, and PortableText conversion.

Closes #222

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

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 a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: Feature: Table support in the editor #217

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5.5 / Codex

Screenshots / test output

Verified:

  • pnpm format
  • pnpm --silent lint:quick
  • pnpm --filter @emdash-cms/admin typecheck
  • pnpm --dir packages/admin exec vitest run --browser.enabled=true tests/components/PortableTextEditor.table.test.ts

Note: full pnpm --silent lint:json | jq '.diagnostics | length' currently reports existing diagnostics outside the files touched by this PR. The changed-file quick lint is clean.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 6, 2026

🦋 Changeset detected

Latest commit: 0ac7f82

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

This PR includes changesets to release 13 packages
Name Type
@emdash-cms/admin Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Scope check

This PR changes 755 lines across 7 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 0ac7f82

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic
Copy link
Copy Markdown
Collaborator

ascorbic commented May 6, 2026

/review

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 6, 2026

Failed to check permissions for user ascorbic: HttpError: Requires authentication - https://docs.github.com/rest

github run

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 6, 2026

@ascorbic Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

>
<BubbleButton
onClick={() => editor.chain().focus().addColumnBefore().run()}
title="Add column before"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this be translated

</BubbleButton>
<BubbleButton
onClick={() => editor.chain().focus().addColumnAfter().run()}
title="Add column after"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also this and all the others

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Nice work porting #222 forward — the PT↔PM round-trip looks correct, per-cell markDefs lines up with the existing Table.astro renderer in @emdash-cms/blocks/core, and the test coverage of round-tripping is solid.

A couple of things to address before merge:

  1. Localization (echoing @ascorbic). Per AGENTS.md ("All admin UI strings, aria labels, and toast messages go through Lingui"), the new title props on the table bubble menu and toolbar button should be wrapped with t..., since `BubbleButton`/`ToolbarButton` propagate `title` to `aria-label` (see `BubbleButton` at line 2579). Yes, the existing surrounding buttons are also unwrapped — but new strings should not extend that pattern; they're untranslated forever once shipped. The slash-command entry already uses `msg`..., so just match that.
  2. Version pinning in pnpm-workspace.yaml. The four new @tiptap/extension-table* entries are pinned to exact 3.20.0 while every other @tiptap/* entry uses ^3.20.0. This will diverge on the next TipTap patch bump and is almost certainly unintentional.

Non-blocking observations inline.

Comment thread pnpm-workspace.yaml Outdated
"@tiptap/extension-table": 3.20.0
"@tiptap/extension-table-cell": 3.20.0
"@tiptap/extension-table-header": 3.20.0
"@tiptap/extension-table-row": 3.20.0
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.

These four are pinned to exact 3.20.0, but every other @tiptap/* catalog entry uses ^3.20.0 (see e.g. @tiptap/extension-link, @tiptap/extension-placeholder, @tiptap/extension-text-align just above and below). On the next TipTap patch release the table extensions will fall behind the rest of the toolchain, which can break the peer-dep linkage with @tiptap/core/@tiptap/pm.

Suggested change
"@tiptap/extension-table-row": 3.20.0
"@tiptap/extension-table": ^3.20.0
"@tiptap/extension-table-cell": ^3.20.0
"@tiptap/extension-table-header": ^3.20.0
"@tiptap/extension-table-row": ^3.20.0

>
<BubbleButton
onClick={() => editor.chain().focus().addColumnBefore().run()}
title="Add column before"
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.

Per AGENTS.md these need to go through Lingui — BubbleButton forwards title to both the native tooltip and aria-label, so this string is read by screen readers. Same applies to every title="..." literal in this new TableBubbleMenu (lines 2505, 2512, 2519, 2528, 2535, 2540, 2549, 2553) and the new toolbar entry at line 2836 (title="Insert Table").

This component already uses useLingui (see line 1884), so:

Suggested change
title="Add column before"
title={t`Add column before`}

and similar for the others. The slash-command entry above (line 935) already uses msg...``, so the i18n pattern for this PR is established — just propagate it to the buttons.


<BubbleButton
onClick={() => editor.chain().focus().toggleHeaderRow().run()}
active={editor.isActive("tableHeader")}
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.

Minor UX note (non-blocking): editor.isActive("tableHeader") reflects whether the cursor is currently inside a <th>, not whether the table has a header row. So the toggle button will look "off" whenever the user is in a body cell — even on a table that does have a header row. If you want the indicator to track the table's header-row state, you'd need to walk the table node and check if its first row contains any tableHeader cells. Fine to leave for a follow-up.

_type: "table",
_key: tableKey,
rows,
hasHeaderRow: rows[0]?.cells.some((cell) => cell.isHeader) ?? false,
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.

hasHeaderRow is derived as "row 0 has any header cell". Combined with the read path at line 667 (cell.isHeader || (tableBlock.hasHeaderRow && rowIndex === 0)), a table where row 0 is a mix of <th> and <td> will round-trip with the entire row promoted to headers on the next load. Probably OK — toggleHeaderRow flips the whole row in TipTap — but worth noting if anyone later adds toggleHeaderCell to the bubble menu.

import {
_portableTextToProsemirror,
_prosemirrorToPortableText,
} from "../../src/components/PortableTextEditor";
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.

These are pure conversion-function tests — nothing here actually exercises a browser environment. Worth pulling them out of the browser test target so they run in the cheap node suite (the PR description notes the file is currently run with --browser.enabled=true). Non-blocking.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 6, 2026

Review posted successfully. Summary:

  • Localization (echoing ascorbic's review): The new title props on TableBubbleMenu buttons and the Insert Table toolbar button propagate to aria-label via BubbleButton/ToolbarButton and need t\...`per AGENTS.md. The slash-command entry already usesmsg`...``, so the pattern is established.
  • Version pinning: The four new @tiptap/extension-table* catalog entries are pinned to exact 3.20.0 while every other @tiptap/* entry uses ^3.20.0 — almost certainly accidental and will cause peer-dep drift.
  • Toggle header active state (nit): editor.isActive("tableHeader") only checks if the cursor is in a <th>, not whether the table has a header row.
  • hasHeaderRow derivation: A row 0 with mixed <th>/<td> round-trips with the whole row promoted to headers — fine for current toggle behavior, flagged for future toggleHeaderCell.
  • Test placement (nit): The conversion tests don't need the browser env and could move to the node suite.

github run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
packages/admin/src/locales/ja/messages.po Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@jcheese1 jcheese1 force-pushed the codex-table-support branch from 393875f to 0ac7f82 Compare May 6, 2026 11:52
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic left a comment

Choose a reason for hiding this comment

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

Thanks for the additions!

@ascorbic ascorbic merged commit 530b013 into emdash-cms:main May 6, 2026
29 checks passed
@emdashbot emdashbot Bot mentioned this pull request May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants