[6.x] Entry index page#19212
Open
brianjhanson wants to merge 115 commits into
Open
Conversation
I ran into some issues going full inertia, namely that elements are able to render their own HTML within the table, which got me scared. This takes a step back and tries to find a different middle ground for rendering the table. I'm still not sure it's right though
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…element-indexes-entries-assets-users-convert-to
…o brian/cms-2148-cp-element-indexes-entries-assets-users-convert-to
…element-indexes-entries-assets-users-convert-to
…element-indexes-entries-assets-users-convert-to
…hub.com:craftcms/cms into brian/cms-2148-cp-element-indexes-entries-assets-users-convert-to
…element-indexes-entries-assets-users-convert-to
…element-indexes-entries-assets-users-convert-to
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The New entry button now falls back to a section menu when the selected source isn't a creatable section (e.g. All entries / singles). Source links carry real hrefs so switching sources updates the URL query, and index columns use accessors so sortable headers render sort buttons. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Folds the full element index feature set from the previous branch into the ElementIndexes-service architecture: - Frontend modules/elements: BaseElementIndex shell + DataTable + cards view, selection (shift-click ranges, keyboard nav), bulk actions bar, view-mode switching, per-source column/sort customization persisted to localStorage, sources sidebar, and the publishable-sections New entry button; AdminTable becomes a thin wrapper over the shared shell. - Backend: ContentIndexController now delegates query building, view state, pagination (with page clamping), bulk-action serialization, column/sort metadata, and row/card serialization to the ElementIndexes service; EntriesIndexController absorbs the section-handle redirect. - CP package: craft-badge, craft-thumbnail, craft-popover, craft-empty, status/size/volume constants, attrs() utility, thumbnail loader, and a global :focus-visible keyboard-focus indicator. - Fixes from the fold: table cells render through the element's getAttributeHtml() pipeline (the renderer bypass crashed on Entry's authors attribute under Pro); stale tests for the branch's abandoned ElementIndexService/Params classes removed; thumbnail markup tests updated for craft-thumbnail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stories for BaseElementIndex, DataTable, ElementCards, AdminTable, and ElementStatus under an Elements group, backed by shared sample-entry fixtures with a client-side TanStack table so selection, sorting, and pagination are interactive in isolation. An Overview MDX documents the module architecture, the useElementIndexSelection composable, bulk actions, and accessibility behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Register content/{section}/new before the index route it was shadowed by
- Remove dead code: DisplayedInIndex::applyIndexSort(), the unused
criteria/contentHtml/canHaveDrafts/baseSortOptions page props, and the
Craft.defaultIndexCriteria default that coupled the page to the legacy
bootstrap
- Render only the visible table columns per row (client sends its column
selection; indexData() eager-load-preps exactly what renders) and
refetch rows when columns are toggled, restoring persisted selections
into the URL on load
- Serialize real sort options (element-type + field-layout, string
attributes only) and drive the View popover's Sort select from them,
starting a newly chosen attribute at its defaultDir
- Select the source for section-handle URLs, including the singles
source, and redirect entries/singles accordingly
- Method-inject ElementIndexRequest in all six element-index XHR
controllers (route-cached controller instances froze the
constructor-injected request across in-process requests)
- Type the table prop as TanStack's Table<any> across the index
components, fixing the undefined-row and CSS-var typing gaps it exposed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… into feature/ineria-element-indexes-filter-hud Resolves the ContentIndexController conflict by adopting the ElementIndexes service refactor and re-applying the filter HUD condition to the element query via a new $condition parameter on ElementIndexes::buildQuery(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m:craftcms/cms into feature/element-index-fable
Copy mirrors Craft 5: it runs client-side, storing the selection in localStorage via Craft.cp.copyElements() so paste targets (nested element managers) and other tabs see it. The serializer emits it as an 'event' action (craft:copy-elements); the bulk-actions bar merges the live selection into the event detail and owns the listener. Non-copy actions are untouched. Also creates the #notifications container on Inertia pages (outside the Vue root) and re-points the legacy CP singleton's cached reference — all legacy notifications, including the persistent 'Entry copied' one, were silently no-oping without it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
These were committed on the old element-indexes branch and carried in by the merge; builds now output to cms-assets/resources/build (already ignored), so nothing writes here anymore. origin/6.x has no resources/build at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The composables and ElementSources no longer import the content/index
wayfinder helper. The page supplies a minimal ElementIndexRoute
({url(query)}) and a shared createIndexVisitor owns the visit plumbing —
current-query merging (including stripping serialized key[0][...] forms,
which the sort and view-mode composables previously lacked), and the
preserveState/preserveScroll/only/replace semantics.
Also fixes two latent bugs the coupling hid: ElementSources hardcoded
{page: 'entries'}, so source links on any other content page pointed back
to the entries page; and the page never passed viewMode to
ElementSources, so its documented view-mode carry-through never ran.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brianjhanson
commented
Jul 6, 2026
One kernel, two formatters: the trait's domain logic (source resolution, query-state building with source criteria/conditions/filters/collapsed descendants, filter-HUD query params, exporter availability) now lives on ElementIndexes with explicit parameters, and the request-shape knowledge (viewState, fieldLayouts, returnUrl, criteria, filter config, collapsed ids) moves onto ElementIndexRequest as parsing helpers. All six consumers — ContentIndexController, the element-index XHR controllers, and ElementIndexResource — consume the service directly, so the Inertia page and the legacy getElements() path share query building rather than parallel copies. The controllers' stashed $this->request property (a workaround for route-cached controller instances) is gone; actions read their method-injected request and pass values explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors Craft 5: each time the table-columns popover opens, the checked columns group to the top (keeping their display order) so they're easy to drag into new positions. The list is an open-time snapshot — toggling checkboxes while it's open never re-sorts rows out from under the pointer; only the next open regroups. The snapshot refreshes when the available column set changes (source switch), keyed on option values rather than array identity so recomputes from checkbox toggles don't trigger a regroup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The persisted per-source `visible` array now drives everything: the table renders columns in that order, checking a column appends it to the end (of the table and the persisted order — the checkbox itself stays put in the open popover), and dragging in the View popover rewrites the order to what's displayed. Unchecked columns carry no persisted order and always present alphabetically below the visible block. This replaces the separate per-source `order` array, whose natural/server ordering leaked into the popover's top block and made items appear to shift between closes and opens. The popover snapshot now refreshes on a set-change signature (sorted values), since the canonical option order itself changes on every toggle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The HTTP-bound payload assembly moves off the ElementIndexes service into a ContentIndexViewModel (following the existing ViewModel pattern: public zero-arg methods are payload keys, typed for the TypeScript transformer). Shared intermediates — resolved source, query, indexData, paginator — are memoized privately since payload methods can run in any order, with the indexData-before-paginate ordering encoded explicitly. ElementIndexes slims to the kernel shared by every index surface (source resolution, query-state building, filter-HUD params, exporters), and the controller drops to hook wiring + the view model. Also fixes a serialization bug the swap surfaced: the controller merged hook context and payload with Arr::merge, whose recursive semantics interleaved the hooks' legacy assoc-keyed tableColumns with the payload's list into a JSON object (previously masked by the value being a Collection). Now a plain array_merge with the view model last, pinned by a list-shape regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collection's TValue template is invariant under PHPStan, so the
branch-narrowed member types produced inside map() could never satisfy a
declared Collection<int, array{...}> shape. Array shapes are covariant,
and both consumers immediately converted the Collection to an array
anyway.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
riasvdv
reviewed
Jul 7, 2026
| * | ||
| * @throws AuthenticationException | ||
| */ | ||
| public function getPublishableSections(): array |
Contributor
There was a problem hiding this comment.
Would it make more sense to have this return Section objects and do the actual mapping to an array (or a resource?) in the controller/viewmodel?
Contributor
Author
There was a problem hiding this comment.
Refactored in c2faa27 but I'm not sure how I feel about using a resource here, mostly because we're not really tied to a request (and resources seem to want to be).
Do you have any feelings about it?
Contributor
There was a problem hiding this comment.
It seems to be used in all request related things (Cp, ViewModel) so a resource seems fine there for me, the goal of building the array is also so it can be consumed by the frontend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the entry index page over to inertia and Vue. Not 100% yet, but it's a big enough chunk of work and works well enough that I think it's worth a merge.
In Craft 5, when an element index was rendered, an XHR request with data plucked from local storage was immediately sent back to the server with information about how we wanted to render that view. With Vue and inertia, the initial Vue is now rendered with the data up front.
Element indexes are made up of a hand full of composables that will cover off on the functionality. All of these composables are route agnostic, so hopefully we'll be able to re-use them to provide their functionality to any table or route in the future and not have everything coupled as tightly as it is today.
Misc. updates
nameattribute to a<craft-button-group/>it will essentially function as a radio button.craft-action-menunow extendscraft-popoverwhich is a generic popover web component. Much like the corresponding element in Web Awesomecraft-buttonwill now only apply icon styles wheniconis empty which will now allow you to pass in aniconname to create a button with an icon prefix or suffix<craft-button/>now supports anicon-positionattribute which you can use to render the icon in either theprefixorsuffixslot<craft-checkbox/>now supports thelabel-sr-onlyproperty<craft-nav-item/>s can now disable the subnav toggle or render it into a prefix slotElementIndexRequest; Laravel caches controller instances on the Route object, so constructor-injected FormRequests served stale data across in-process requests.Craft.cp.displayNotification, the "Entry copied" notice) were silently no-oping on Inertia pages — the#notificationscontainer now gets created outside the Vue root at bootstrap.Not yet implemented
The legacy Twig index and
element-indexes/*endpoints remain untouched for all other element types and modal/field contexts.