feat(media-folder-pro): add virtual folder system for WordPress media library

Custom WordPress plugin that replaces the default flat media library with
a structured folder view. Features: hierarchical folders via custom taxonomy,
sidebar folder tree, drag & drop, modal integration with Elementor/builders,
bulk assign, upload auto-assign, toast notifications.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 14:08:49 +01:00
parent 4ad3303b18
commit 5014b9108f
19 changed files with 3692 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
---
phase: 02-media-library-grid
plan: 01
type: execute
wave: 1
depends_on: ["01-01"]
files_modified:
- wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php
- wp-content/plugins/media-folder-pro/includes/class-media-query.php
- wp-content/plugins/media-folder-pro/assets/js/folder-tree.js
- wp-content/plugins/media-folder-pro/assets/js/media-filter.js
- wp-content/plugins/media-folder-pro/assets/css/admin.css
- wp-content/plugins/media-folder-pro/media-folder-pro.php
autonomous: true
---
<objective>
## Goal
Podlaczyc drzewko folderow do filtrowania biblioteki mediow WP oraz dodac drag & drop przypisywanie mediow do folderow. Po kliknieciu folderu — grid pokazuje tylko media z tego folderu. Przeciagniecie media na folder przypisuje je.
## Purpose
Bez filtrowania i przypisywania foldery sa bezuzyteczne — to core feature pluginu.
## Output
- Filtrowanie media grid po kliknieciu folderu w sidebar
- AJAX endpoint do przypisywania mediow do folderow
- Drag & drop mediow z grida na folder w drzewku
- Aktualizacja counterow folderow po zmianach
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Prior Work
@.paul/phases/01-media-folders-plugin/01-01-SUMMARY.md
- MFP_Taxonomy z media_folder taxonomy
- MFP_Ajax_Handler z 5 CRUD endpoints
- folder-tree.js dispatches CustomEvent 'mfp-folder-selected'
- Sidebar z drzewkiem juz renderuje sie na upload.php
## Source Files
@wp-content/plugins/media-folder-pro/media-folder-pro.php
@wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php
@wp-content/plugins/media-folder-pro/includes/class-taxonomy.php
@wp-content/plugins/media-folder-pro/assets/js/folder-tree.js
@wp-content/plugins/media-folder-pro/assets/css/admin.css
</context>
<acceptance_criteria>
## AC-1: Filtrowanie mediow po kliknieciu folderu
```gherkin
Given plugin aktywny, istnieja foldery z przypisanymi mediami
When uzytkownik klika folder "Zdjecia produktow" w drzewku
Then grid mediow pokazuje tylko attachmenty przypisane do tego folderu
And klikniecie "Wszystkie media" przywraca pelna liste
```
## AC-2: Przypisywanie mediow do folderow przez AJAX
```gherkin
Given plugin aktywny, istnieje folder i media bez folderu
When wywolany zostaje AJAX mfp_assign_media z attachment_ids i folder_id
Then attachmenty zostaja przypisane do taxonomy term media_folder
And odpowiedz zawiera liczbe przypisanych mediow
```
## AC-3: Drag & drop mediow na foldery
```gherkin
Given plugin aktywny, widoczny grid mediow i drzewko folderow
When uzytkownik przeciaga miniature media na folder w drzewku
Then media zostaje przypisane do tego folderu
And counter folderu aktualizuje sie
And grid odswieza sie jesli aktywny jest inny folder
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Server-side media query filter + assign endpoint</name>
<files>wp-content/plugins/media-folder-pro/includes/class-media-query.php, wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php, wp-content/plugins/media-folder-pro/media-folder-pro.php</files>
<action>
1. Utworzyc includes/class-media-query.php — klasa MFP_Media_Query:
- Hook na 'ajax_query_attachments_args' (filtr WP uzywany przez media grid)
- Jesli w $_REQUEST istnieje parametr 'media_folder' (int > 0):
- Dodaj tax_query do args: taxonomy=media_folder, terms=[folder_id]
- Jesli media_folder === -1 (uncategorized):
- Dodaj tax_query z operator NOT EXISTS (media bez folderu)
- Metoda register(): add_filter('ajax_query_attachments_args', ...)
2. W class-ajax-handler.php dodac nowy endpoint mfp_assign_media:
- Parametry: attachment_ids (array int), folder_id (int, 0 = usun z folderow)
- Dla kazdego attachment_id:
- Jesli folder_id > 0: wp_set_object_terms($attachment_id, [$folder_id], 'media_folder')
- Jesli folder_id === 0: wp_set_object_terms($attachment_id, [], 'media_folder')
- Zwraca: {success: true, count: N, folder_counts: {id: count, ...}}
- folder_counts: zaktualizowane countery wszystkich folderow (dla odswiezenia UI)
- Dodac 'mfp_assign_media' do tablicy $actions w register_hooks()
3. W media-folder-pro.php:
- require_once class-media-query.php
- Utworzyc instancje MFP_Media_Query w konstruktorze
- Wywolac register() w konstruktorze (nie potrzebuje hooka init)
Avoid: Nie modyfikowac globalnego WP_Query — tylko ajax_query_attachments_args.
Avoid: Nie uzywac append=true w wp_set_object_terms — kazdy media ma dokladnie 1 folder (lub 0).
</action>
<verify>
- AJAX query-attachments z parametrem media_folder=ID zwraca tylko media z tego folderu
- AJAX mfp_assign_media przypisuje media i zwraca success
- Bez parametru media_folder — zwraca wszystkie media (brak regresji)
</verify>
<done>AC-1 (server-side) + AC-2 satisfied: filtrowanie i przypisywanie dziala na backendzie</done>
</task>
<task type="auto">
<name>Task 2: Media filter JS + drag & drop</name>
<files>wp-content/plugins/media-folder-pro/assets/js/media-filter.js, wp-content/plugins/media-folder-pro/assets/js/folder-tree.js, wp-content/plugins/media-folder-pro/assets/css/admin.css, wp-content/plugins/media-folder-pro/media-folder-pro.php</files>
<action>
1. Utworzyc assets/js/media-filter.js — integracja z WP media grid:
- Nasluchuj na 'mfp-folder-selected' CustomEvent
- Przy wyborze folderu:
- Pobierz wp.media.frame (AttachmentsBrowser) jesli istnieje
- Ustaw props modelu kolekcji: collection.props.set({media_folder: folderId})
- To wymusi AJAX reload z nowym parametrem
- Przy "Wszystkie media" (folderId === null):
- collection.props.unset('media_folder') lub set media_folder do 0
- Fallback: jesli wp.media nie istnieje (list view), uzyj window.location z query param
2. Drag & drop w media-filter.js:
- Na elementach .attachment w grid: dodaj draggable=true
- MutationObserver na kontenerze .attachments aby lapac nowo dodane elementy
- dragstart: ustaw dataTransfer z attachment ID (z data-id atrybutu)
- Dodaj klase 'mfp-dragging' na body
- dragend: usun klase 'mfp-dragging'
3. W folder-tree.js dodac obsluge drop:
- Na kazdym .mfp-folder__row: dragover (preventDefault + klasa 'mfp-drop-target')
- dragleave: usun klase 'mfp-drop-target'
- drop: odczytaj attachment ID z dataTransfer
- Wywolaj AJAX mfp_assign_media
- Po sukcesie: refreshTree() (odswiezy countery)
- Jesli aktywny folder != docelowy: refresh grid (dispatchEvent)
- Wyeksportuj refreshTree jako window.mfpRefreshTree aby media-filter.js moglo go wywolac
4. W admin.css dodac style:
- .mfp-drop-target: niebieskie podswietlenie folderu podczas drag over
- .mfp-dragging .mfp-folder__row: subtelny hover indicator
- .attachment[draggable] cursor: grab
5. W media-folder-pro.php:
- Enqueue media-filter.js z dependency ['media-views'] (WP media JS)
- Dodac i18n strings: 'assignSuccess', 'assignError'
Avoid: Nie uzywac jQuery UI Draggable/Droppable — czysty HTML5 Drag & Drop API.
Avoid: Nie modyfikowac WP core JS — tylko hookujemy sie przez props.set().
</action>
<verify>
- Klikniecie folderu filtruje grid (widac tylko media z folderu)
- Klikniecie "Wszystkie media" przywraca wszystko
- Przeciagniecie miniaturki na folder przypisuje media
- Counter folderu aktualizuje sie po drop
- Brak JS errors w konsoli
</verify>
<done>AC-1 (client-side) + AC-3 satisfied: filtrowanie i drag & drop dzialaja w UI</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- wp-content/plugins/elementor-pro/* (nie modyfikujemy Elementora)
- wp-content/plugins/media-folder-pro/includes/class-taxonomy.php (stabilna z Phase 1)
- Zadne pliki core WordPress
## SCOPE LIMITS
- Brak integracji z media modal (Phase 3)
- Brak bulk operations z UI (Phase 4)
- Brak drag & drop folderow miedzy soba (juz jest w AJAX, UI w Phase 4)
- List view (wp_list_table) — tylko grid view w tej fazie
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Klikniecie folderu filtruje media grid
- [ ] "Wszystkie media" przywraca pelna liste
- [ ] mfp_assign_media endpoint dziala z nonce verification
- [ ] Drag & drop media na folder przypisuje i odswierza countery
- [ ] Brak regresji: media grid dziala normalnie bez aktywnego filtra
- [ ] Brak JS errors w konsoli przegladarki
- [ ] Assets ladowane z poprawna kolejnoscia (media-views przed media-filter)
</verification>
<success_criteria>
- Wszystkie taski ukonczone
- Wszystkie verification checks przeszly
- Brak bledow PHP i JS
- Filtrowanie + drag & drop dzialaja plynnie
</success_criteria>
<output>
After completion, create `.paul/phases/02-media-library-grid/02-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,119 @@
---
phase: 02-media-library-grid
plan: 01
subsystem: media
tags: [wordpress, media-grid, drag-drop, taxonomy-filter, backbone-js]
requires:
- phase: 01-media-folders-plugin
provides: media_folder taxonomy, AJAX CRUD, folder tree UI
provides:
- Media grid filtering by folder via WP backbone props
- mfp_assign_media AJAX endpoint
- HTML5 drag & drop media to folders
affects: [03-media-modal, 04-polish-ux]
tech-stack:
added: []
patterns: [ajax_query_attachments_args filter, wp.media.props integration, HTML5 drag&drop, MutationObserver for dynamic content]
key-files:
created:
- wp-content/plugins/media-folder-pro/includes/class-media-query.php
- wp-content/plugins/media-folder-pro/assets/js/media-filter.js
modified:
- wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php
- wp-content/plugins/media-folder-pro/assets/js/folder-tree.js
- wp-content/plugins/media-folder-pro/assets/css/admin.css
- wp-content/plugins/media-folder-pro/media-folder-pro.php
key-decisions:
- "Filter via ajax_query_attachments_args — no core WP modifications"
- "wp.media library.props.set() triggers grid reload natively"
- "HTML5 Drag & Drop API — no jQuery UI dependency"
- "MutationObserver to catch dynamically loaded attachments (infinite scroll)"
- "Drop on 'All Media' unassigns from folder (folder_id=0)"
patterns-established:
- "window.mfpRefreshTree() exposed for cross-module communication"
- "media-filter.js depends on media-views + media-folder-pro-tree"
- "Delegated drag events on #mfp-folder-root for drop targets"
duration: ~10min
started: 2026-03-28
completed: 2026-03-28
---
# Phase 2 Plan 01: Media Library Grid Integration Summary
**Media grid filtering by folder click + drag & drop media assignment with live counter updates, integrated via WP backbone props and HTML5 Drag & Drop API.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Tasks | 2 completed |
| Files created | 2 |
| Files modified | 4 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Filtrowanie mediow po kliknieciu folderu | Pass | ajax_query_attachments_args + library.props.set |
| AC-2: Przypisywanie mediow przez AJAX | Pass | mfp_assign_media with folder_counts response |
| AC-3: Drag & drop mediow na foldery | Pass | HTML5 D&D + MutationObserver + counter refresh |
## Accomplishments
- Server-side filter via `ajax_query_attachments_args` hook — supports folder filtering and "uncategorized" (id=-1)
- `mfp_assign_media` endpoint assigns media to exactly 1 folder (or removes) and returns updated folder counts
- HTML5 drag & drop from media grid thumbnails to folder tree with visual feedback
- Drop on "All Media" removes folder assignment
- MutationObserver catches dynamically loaded attachments for draggable setup
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `includes/class-media-query.php` | Created | WP attachment query filter by media_folder taxonomy |
| `assets/js/media-filter.js` | Created | Grid filter via wp.media props + drag & drop logic |
| `includes/class-ajax-handler.php` | Modified | Added mfp_assign_media endpoint |
| `assets/js/folder-tree.js` | Modified | Exposed window.mfpRefreshTree() |
| `assets/css/admin.css` | Modified | Drag & drop visual styles (drop target, cursors) |
| `media-folder-pro.php` | Modified | MFP_Media_Query init, media-filter.js enqueue, i18n strings |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Use ajax_query_attachments_args | Native WP filter, no core mods | Clean integration |
| library.props.set for grid reload | Backbone native, triggers AJAX automatically | No manual DOM manipulation |
| MutationObserver for infinite scroll | Attachments load dynamically, need draggable on each | Robust for any load pattern |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Full folder filtering + assignment pipeline operational
- media-filter.js pattern reusable for modal integration (Phase 3)
- mfp_assign_media endpoint ready for bulk use (Phase 4)
**Concerns:**
- Media modal uses different wp.media frame — will need separate integration path
- List view (table) not yet handled (deferred to Phase 4)
**Blockers:**
- None
---
*Phase: 02-media-library-grid, Plan: 01*
*Completed: 2026-03-28*