links as blocks - #28
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved portal focus bugs and import, child-filtering, and hook-ordering issues require fixes before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (3)
What changed in this PR
Refactors blogroll links from a legacy attribute array into nested blockroll/link blocks while preserving server rendering, OPML export, source integration, and migration.
Changes:
- Adds editable link blocks with overlays, media controls, discovery, import, and duplication.
- Updates rendering, OPML extraction, migration, tests, and documentation.
- Rebuilds assets and updates dependencies and CI configuration.
| File | Description |
|---|---|
tests/test-render.php |
Tests nested link rendering and legacy fallback. |
tests/test-opml.php |
Tests OPML extraction from link blocks. |
src/link/overlay-button.js |
Adds metadata overlays; portal focus handling needs correction. |
src/link/index.js |
Registers the link block. |
src/link/editor.scss |
Styles link block editing. |
src/link/edit.js |
Implements link editing and overlays; link overlay focus handling needs correction. |
src/link/block.json |
Defines link block metadata and attributes. |
src/blogroll/utils.js |
Adds URL, migration, and duplicate utilities. |
src/blogroll/render.php |
Passes nested blocks to server rendering. |
src/blogroll/link-block.js |
Creates link block instances. |
src/blogroll/index.js |
Enables inner blocks and deprecations. |
src/blogroll/editor.scss |
Styles blogroll editing controls. |
src/blogroll/edit.js |
Manages nested links, imports, and insertion; child filtering and hook ordering need fixes. |
src/blogroll/discover.js |
Adds cancellable discovery requests. |
src/blogroll/deprecated.js |
Migrates legacy link attributes. |
src/blogroll/components/link-form.js |
Removes the superseded link form. |
src/blogroll/components/import-modal.js |
Adds cancellable import enrichment; pending requests need abort handling. |
src/blogroll/components/address-form.js |
Adds the reusable address form. |
src/blogroll/components/add-link.js |
Adds address lookup; popover portal focus handling needs correction. |
src/blogroll/block.json |
Provides avatar context. |
src/blogroll/__tests__/utils.test.js |
Updates utility tests. |
src/blogroll/__tests__/to-url.test.js |
Tests URL normalization. |
src/blogroll/__tests__/site-key.test.js |
Tests duplicate detection keys. |
src/blogroll/__tests__/migrate.test.js |
Tests legacy migration helpers. |
readme.md |
Documents block-based link editing. |
package.json |
Adds the Compose dependency; dependency placement needs adjustment. |
package-lock.json |
Updates dependency resolution. |
includes/class-sources.php |
Resolves links from nested blocks. |
includes/class-opml.php |
Exports nested link blocks. |
eslint.config.cjs |
Configures the experimental focus hook. |
docs/developers.md |
Documents the new data model. |
build/link/index.js |
Built link block script. |
build/link/index.css |
Built link block styles. |
build/link/index.asset.php |
Link script dependencies. |
build/link/index-rtl.css |
RTL link styles. |
build/link/block.json |
Built link metadata. |
build/blogroll/render.php |
Built render template. |
build/blogroll/index.js |
Built blogroll script. |
build/blogroll/index.css |
Built blogroll editor styles. |
build/blogroll/index.asset.php |
Blogroll script dependencies. |
build/blogroll/index-rtl.css |
RTL blogroll styles. |
build/blogroll/block.json |
Built blogroll metadata. |
blockroll.php |
Registers both blocks and skips child rendering. |
.github/workflows/phpunit.yml |
Updates the supported WordPress test configuration. |
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The editor can misrepresent empty blogrolls when non-link inner blocks are present.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Resolved since last review (3)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Five moderate findings remain regarding version configuration, empty submissions, import cancellation, source fallback, and block-props initialization.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
Resolved since last review (1)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved moderate findings remain in validation, child-block handling, hook ordering, URL normalization, and accessibility.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (2)
Resolved since last review (1)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Four moderate findings and one scope/documentation nit remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 3
Open (3)
Resolved since last review (2)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Legacy migration can leave editor and server views inconsistent, and the deprecation wiring needs focused end-to-end coverage.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
Resolved since last review (3)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A critical URL-scheme validation issue can allow javascript: links to execute in the editor.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
Resolved since last review (1)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (3)
Resolved since last review (1)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The asynchronous lookup callback can overwrite a newer user-edited Added date.
Review effort: Lite
Findings: None
Resolved since last review (3)
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Correct the block-props hook order and add direct deprecation migration coverage.
Review effort: Lite
Findings: None
Files not reviewed (6)
- build/blogroll/index-rtl.css: Generated file
- build/blogroll/index.css: Generated file
- build/blogroll/index.js: Generated file
- build/link/index-rtl.css: Generated file
- build/link/index.css: Generated file
- build/link/index.js: Generated file



Every link of a blogroll is a block of its own now,
blockroll/link, inside the blogroll block. Adding, moving, duplicating and copying a link to another page works the way it does with any block, and everything is edited where it shows: name and description in place, the address behind the name in core's link overlay, feed and relationship in small overlays on the line below, the icon with a click on it (core's media menu). "Add link" opens an overlay with the address, looks the site up and fills in what it finds, like the old form's "Fetch details" did. Import is unchanged and puts the links in as blocks. The idea comes from the Jetpack blogroll block.The frontend, the OPML, the discovery links and the source registry do not change: the blogroll still renders the whole list on the server, the link block has no output of its own.
Sources::links()reads the link blocks and falls back to the oldlinksattribute.Migration in two layers: a post nobody opens again keeps rendering from the attribute, and a post that is opened gets its links turned into link blocks by a block deprecation (
src/blogroll/deprecated.js). Core does not mark the post changed for that, so the new shape is written with the next edit.Two things I ran into, both with a comment in the code: the container
useInnerBlocksPropsreturns has to be rendered even for an empty list, since it is what registers the block list settings a link block needs to be inserted. And aMediaReplaceFlowwhose toggle sits in the canvas iframe closes on the media library, because its focus check only knows the iframe's document; that unmounts the media control and leaves an empty modal, so the check is done against the parent document instead.Not in here yet: a hint in the inspector that the canvas shows the list order while "Sort by" is name or date, and the empty placeholders for description, feed and relationship only when the link block is selected.