[6.x] Entry index page#19212
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
Any focusable light-DOM element now gets a visible focus ring on keyboard focus. Placeholder treatment. Supersedes the per-component row/card rules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Row/card :focus-visible styles are now covered by the global rule in base.css, so remove the redundant component-scoped copies. (These two files also carry incidental Prettier reflow from a prior format pass.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
There was a problem hiding this comment.
@riasvdv Feels like you should probably put some eyes on this one. I'm not sure this is the best place for it, but I wanted somewhere to get some logic out of the controller.
There was a problem hiding this comment.
This is all very HTTP bound so I think this could work as a ViewModel, or a base ElementIndexViewModel which can be extended for entries as EntryIndexViewModel, accepting the necessary variables that change the result (like elementType) in its constructor.
Any variables from the request can be retrieved directly inside the ViewModel so wouldn't need to be passed into the methods, or an ElementRequest could be passed into the constructor to make it explicit.
Any public properties and public methods get turned into props through toArray if you extend the base ViewModel
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>
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.