Skip to content

Feature pat filemanager#1592

Open
MrTango wants to merge 41 commits into
masterfrom
feature-pat-filemanager
Open

Feature pat filemanager#1592
MrTango wants to merge 41 commits into
masterfrom
feature-pat-filemanager

Conversation

@MrTango
Copy link
Copy Markdown
Contributor

@MrTango MrTango commented May 27, 2026

  • I signed and returned the Plone Contributor Agreement, and received and accepted an invitation to join a team in the Plone GitHub organization.
  • I verified there aren't any other open pull requests for the same change.
  • I followed the guidelines in Contributing to Plone.
  • I successfully ran code quality checks on my changes locally.
  • I successfully ran tests on my changes locally.
  • If needed, I added new tests for my changes.
  • If needed, I added documentation for my changes.
  • I included a change log entry in my commits.

If your pull request closes an open issue, include the exact text below, immediately followed by the issue number. When your pull request gets merged, then that issue will close automatically.

Closes #

@MrTango MrTango force-pushed the feature-pat-filemanager branch from d1f8425 to a3eabff Compare May 28, 2026 08:01
@MrTango
Copy link
Copy Markdown
Contributor Author

MrTango commented May 31, 2026

This is close to being ready. Only missig feature is the reordering function from pat-structure, which will be added by @petschki. Then we can release this with the next plone.staticresources release, so people can start using it in Plone 6 project, by overriding the folder_contents.pt:

<metal:main fill-slot="main">
        <span tal:replace="structure context/@@authenticator/authenticator"/>
        <div class="pat-filemanager"
          tal:attributes="data-pat-filemanager view/options" />
</metal:main>

@petschki
Copy link
Copy Markdown
Member

petschki commented May 31, 2026

Todays Train Session:

Rearrange implementation

  • like pat-structure had

URL Sync on Folder Navigation

  • Browser URL is updated via history.pushState when navigating into subfolders (the view suffix e.g. /folder_contents is preserved → reload works correctly)
  • Browser Back/Forward navigates the folder structure correctly via popstate

Link Integrity Check on Delete

  • The DELETE action calls GET /@linkintegrity?uids=… against the portal root before deleting (not the current context — the endpoint is only registered for IPloneSiteRoot)
  • With breaches: New LinkIntegrityForm modal lists affected items with their incoming links; "Delete anyway" proceeds with deletion
  • Without breaches: window.confirm() — for folders, also shows the subitem count: "Delete 1 item (including 42 subitems)?"
  • items_total from the API response is shown per item in the modal and surfaced as a combined subItemsTotal in both confirmation paths

UI Polishing

  • Content-type icons: All items in table and grid view now use icons from the Plone registry (contenttype/{normalized_type}) instead of emoji
  • "Up to parent" in table view: First row with dashed bottom border, drop-target support (mirrors grid view)
  • "Up to parent" icon: Bootstrap icon arrow-90deg-left (SVG, 3rem in grid cards)
  • Rearrange button: Moved before the action group in the toolbar
  • Rearrange fields: Only Title and ID (matches pat-structure defaults)
  • Sticky toolbar: Action bar sticks to the top of the viewport while scrolling (position: sticky) so toolbar actions remain accessible in long folder listings
  • Uniform font size: --filemanager-ui-size: 0.875rem applied to toolbar, table headers and pagination
  • Drop-target border: Replaced box-shadow with directional borders — single unbroken rectangle with no cell divisions
  • Bootstrap CSS variable mapping: All colours/spacing use var(--bs-*) with fallbacks; label checkboxes/radios use selector label.filemanager-field-check for specificity (overrides Bootstrap's label:has(input))

current state

Structure Pattern Filemanager
Bildschirmfoto 2026-05-31 um 15 56 11 Bildschirmfoto 2026-05-31 um 16 34 53

@petschki petschki marked this pull request as ready for review May 31, 2026 13:51
MrTango added 25 commits May 31, 2026 16:24
Add a new pat-filemanager pattern built with Svelte 5 runes, covering
content browsing, breadcrumbs, pagination, filtering, upload, clipboard
operations, workflow/rename/tags/properties modals, and configurable
columns, backed by plone.restapi.

Extend the build chain with TypeScript support: babel preset-typescript,
svelte-preprocess in webpack and svelte.config, dedicated webpack rules
for .ts and .svelte.(js|ts), tsconfig.json, and custom Jest transformers
for Svelte components and runes-in-module files.
Store the listing batch size and visible-column config in a cookie
(matching legacy pat-structure) instead of the patternslib localStorage
store, so the view survives reloads the same way the old structure view
did.
Apply Svelte's animate:flip to the listing rows and the column-config
list so drag reordering slides items into place. To satisfy flip's
"immediate child of a keyed each" rule, lift the row <tr> (with its drag
handlers) from ContentRow into ContentTable and drop the now-redundant
ContentRow component.

moveTo now reorders the items array optimistically so the rows flip
right away, then PATCHes the server and reconciles via a silent reload
that does not toggle `loading` — previously the loading placeholder tore
down the keyed rows, so flip never ran.
Update the spec for the post-P6 follow-ups: switch the batch-size and
column persistence notes from localStorage to the cookie-backed storage
util, record that ContentRow was merged into ContentTable so animate:flip
can run on the row element, and add §19 covering the optimistic-reorder /
silent-reload animation fix.
Move the spec into the pattern dir and ignore stamp-yarn.
Render the upload list inside the filemanager-status element with a
derived summary header, a corner close button and ESC-to-close, instead
of a separate panel and a duplicate status message. Drops the redundant
reportUpload() summary.
Add a user-switchable listing view: the existing table plus a new
photo-organizing grid. ViewStore (cookie-persisted, seeded from a new
default-view config arg) drives a ViewSwitcher in the toolbar and the
table/grid swap in App. Shared selection and drag/drop logic is
extracted from ContentTable into a ListInteractions store reused by both
views (unit-tested). ContentGrid renders cards with larger previews via
a thumbnailUrl scale-fallback chain.
Upgrade the hand-rolled role="dialog" overlay to the native <dialog>
element driven by showModal()/close() from an $effect on modal.isOpen.
The browser provides the focus move/trap/restore, ::backdrop, page
inertness and Escape handling, so the manual focus trap and window
keydown handler are gone. ModalStore gains toggle(); the toolbar's
State/Tags/Properties/Rename buttons toggle the dialog and reflect it
with aria-pressed.
Move ColumnsConfig out of the toolbar into the table's actions-column
header: the toggle is now a 3-dots icon button labelled "Column
settings" (aria-haspopup/expanded/title), and its popover opens toward
the table (right: 0) so it doesn't run off the edge. Living in the table
header makes it table-only by construction, so the grid-mode guard in
App is no longer needed.
Add a native Svelte QueryBuilder mirroring pat-structure's QueryString
widget: rows of index/operation/value that serialise to
plone.app.querystring {i,o,v} criteria, sourced from @querystring.
Supported widgets: String, Date, DateRange, RelativeDate,
MultipleSelection, Reference/RelativePath (as text).

Wire extraCriteria through ContentsStore (buildQuery, applyFilters,
clearFilters, hasActiveFilters, navigateTo reset) and host the builder
in a FilterBar popover alongside the existing search and type filters.
…-all

- sort and drag reorder reconcile via a silent reload so the keyed rows
  stay mounted and flip into place instead of snapping
- live drag preview shows an insertion bar in the gap between siblings and
  keeps the dragged item visible
- contiguous multi-row selections drag and drop as one block, committed as
  a sequence of single restapi ordering moves against a working order
- grid view gains a select-all / deselect-all header to reset the selection
…yout

- toolbar actions reordered like pat-structure and rendered as an icon
  button group (Plone @@iconresolver icons); the Delete icon is tinted red
- the selection count is replaced by a reusable select-all/deselect control
  (SelectAll), dropping the separate Clear selection button
- move-into-folder now asks for confirmation through a native <dialog>
  (ConfirmStore/ConfirmDialog), defaulting to and highlighting "Move"
- drop the type filter; move search and the advanced filter to the right of
  the action row, opening the filter popover right-aligned
…avigation to the portal root

Render the breadcrumbs beneath the toolbar instead of above it and
right-align the toolbar controls. Resolve a portalUrl from the
folder_contents urlStructure.base (get_top_site_from_url) so "go up" /
breadcrumb navigation can climb out of a navigation root such as a
plone.app.multilingual language folder back to the portal root.
Add a five-stage (xs–xl) size slider, shown in the filter bar while the
grid view is active, that scales the card min-width via .grid-size-*
classes. The chosen scale is remembered as a cookie preference
(medium by default), kept on the ViewStore alongside the view mode.
… selection, checkbox)

Grid card drag-and-drop and selection refinements:

- Reorder via three-band drag zones (before/into/after) shared with the
  table, and a live insertion marker that tracks the drop gap. At a row
  boundary the marker no longer jumps to an adjacent row: a forward drop
  past a row's last image shows on that card's trailing edge, while a
  backward drop before a row's first image stays on its leading edge.
- Clicking a card toggles its selection (a second click deselects it,
  like Space and the checkbox); the table keeps click-to-replace.
- Replace the custom checkbox with check-circle / check-circle-fill
  icons, inset from the card corner, and drop the review-state marker.
MrTango and others added 14 commits May 31, 2026 16:24
…grid

Replace the custom native-HTML5 reorder (live movePreview/animate:flip
preview, per-row drag handlers, three-band folder-zone geometry, grid
wrap-marker) with sortablejs, wrapped in a small Svelte use: action
(utils/sortable.ts). The reorder/move-into-folder/move-into-parent
decisions stay in the shared ListInteractions controller via dragStart/
dragMove/dragEnd; the action reverts sortablejs's DOM move so Svelte's
keyed each stays the source of truth.

Folders are solid drop targets: dragMove never lets sortablejs swap the
dragged item with a folder, so it stays put under the pointer and the
whole row/card is a reliable move-into target (fixes drag-into-folder in
the vertical table, where row swaps made folders slide away). Reordering
past a folder still works via the next non-folder item.

External file-drop (upload into subfolder/parent) stays on native DOM
events, file-only while a sortablejs drag is active. Verified end-to-end
in a live Plone Classic listing: drag-into-folder (table + grid) and
drag-into-parent (grid).

Also fix the progress dialog showing as an empty bordered box over the
listing: scope its display:flex to [open] so a closed dialog keeps the
UA display:none.
… other actions

Match the toolbar action buttons (1px border, 6px radius, white bg,
same padding) so select-all reads as one of the actions instead of a
bare checkbox + label.
Replace the prev/next text buttons with chevron icons and the per-page
select with a segmented button group (matching ViewSwitcher and
pat-structure), and scroll the app back to the top after paging or
resizing the batch.
…ayout shift

Render greyed-out skeleton rows (table) and cards (grid) during loading
instead of collapsing the listing to a one-line message, so the loaded
content drops into the same space without a jump. A new placeholderCount
getter sizes the skeleton from the previous page's item count (exact when
paging/sorting within a folder), falling back to a modest screenful on a
fresh load. Images already use loading=lazy in both views.
Icons resolved asynchronously and rendered nothing until ready, so action
buttons and rows reflowed as each icon popped in. Always render the sized
icon container so it reserves its final footprint from first paint; the SVG
fills that box once resolveIcon settles.
Add an arrows-move button to each grid card (manual-order only) that puts
the card into move-mode, after which Arrow keys step it one slot backward
(Up/Left) or forward (Down/Right) through the listing. Esc/Enter leaves the
mode; steps past either end are ignored and the card keeps focus across
repeated presses. Reuses the existing optimistic reorder path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each arrow step committed via moveTo, which awaited a server PATCH and a
reload per keystroke. A second press before the round-trip finished sent a
subset_ids from the not-yet-committed order; the server rejected it and the
error path's full (non-silent) reload swapped in the loading skeleton,
destroying the focused card so the keys stopped landing.

Mirror the drag flow instead: each step is a local-only movePreview (instant,
focus kept), and the accumulated reorder is committed once via commitReorder
on leaving move-mode (Escape/Enter, the button, switching cards, or blurring
away). No per-keystroke requests, no skeleton swap, no races.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The keyboard move-mode (arrows-move button + Arrow-key repositioning) did not
work well in practice, so back it out. Removes the move button, the is-moving
styling, and the ListInteractions move-mode logic and tests, returning the grid
to drag-and-drop reordering only. Keeps the unrelated unselected-card icon
change (check-circle -> circle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(pat filemanager): item hover background.
@petschki petschki force-pushed the feature-pat-filemanager branch from 5dfe754 to 7e79f09 Compare May 31, 2026 14:24
petschki and others added 2 commits June 1, 2026 09:33
Add §25 covering the link-integrity warning on delete (checkLinkIntegrity
API, ModalStore data payload, Toolbar delete flow, LinkIntegrityForm).
Update §16 to drop the link-integrity item from planned features and
§24 to reflect the trimmed Title/ID rearrange sort options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntents

Dropping an OS folder (onto the listing, a subfolder row, or the "up to
parent" card) now recreates the folder tree in Plone and uploads every
nested file, after a calculated preview + approval step. Plain file drops
are unchanged (immediate upload, no preview).

dataTransfer.files is a flat FileList that silently omits directories, so
dragging a folder previously uploaded nothing. Read directories via the
webkitGetAsEntry() entries API instead.

- utils/dropentries.ts: capture top-level entries synchronously during the
  drop, walk them into a DropManifest (files+relative paths, dirs
  parents-first, counts/size); drains the paginated readEntries(); degrades
  to the flat path when the API is absent.
- api/upload.js createFolder() + UploadStore.uploadTree(): create folders
  parents-first (mapping each path to the real @id), upload files into them
  reusing the existing per-file progress panel; folder-create failures
  orphan descendants without aborting the batch.
- FolderDropStore + FolderDropPreview.svelte: approval gate modelled on
  ConfirmStore/ConfirmDialog; shows summary + indented folder tree.
- ListInteractions.handleExternalDrop(): single orchestrator that all
  external drops (UploadZone, subfolder row, parent card) route through.
- Configurable folderType option (parser arg folder-type, default "Folder").
- Tests for dropentries, uploadTree, createFolder, FolderDropStore; CSS;
  README + spec updates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@petschki
Copy link
Copy Markdown
Member

petschki commented Jun 1, 2026

feature implementation: Folder drop → recreate structure & upload contents

Dropping an OS folder (onto the list, a subfolder row, or the "up one level" card) now recreates the folder tree in Plone and uploads all nested files — after a preview + approval step. Plain file drops are unchanged.

Why: dataTransfer.files silently omits directories, so dragging a folder uploaded nothing. Now read via the webkitGetAsEntry() entries API.

Changes

  • utils/dropentries.ts (new): walks dropped dirs into a manifest (files+paths, folders parents-first, counts/size); drains paginated readEntries(); falls back to flat upload without the API.
  • api/upload.js createFolder() + UploadStore.uploadTree(): create folders parents-first, upload files into them (reusing the progress panel); create-failures don't abort the batch.
  • FolderDropStore + FolderDropPreview.svelte (new): approval dialog (à la ConfirmStore) showing summary + folder tree.
  • ListInteractions.handleExternalDrop(): single orchestrator for all external drops.
  • New folderType option (folder-type, default "Folder").

Tests/build: new tests for dropentries / uploadTree / createFolder / FolderDropStore; full suite green; dev build compiles.

Note: folder reading works on drag&drop only, not the upload button (webkitdirectory would be a separate change).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants