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:
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Media Folder Pro — Modal Integration
|
||||
* Adds folder dropdown filter to wp.media modal and auto-assigns uploads.
|
||||
*/
|
||||
( function () {
|
||||
'use strict';
|
||||
|
||||
if ( ! window.wp || ! wp.media || ! wp.media.view ) return;
|
||||
|
||||
const { ajaxUrl, nonce, i18n } = window.mfpData || {};
|
||||
if ( ! ajaxUrl ) return;
|
||||
|
||||
let activeFolderId = 0;
|
||||
let foldersCache = null;
|
||||
|
||||
// ─── AJAX helper ──────────────────────────────────────────
|
||||
function ajax( action, data ) {
|
||||
data = data || {};
|
||||
const body = new URLSearchParams();
|
||||
body.append( 'action', action );
|
||||
body.append( 'nonce', nonce );
|
||||
for ( const [ k, v ] of Object.entries( data ) ) {
|
||||
body.append( k, v );
|
||||
}
|
||||
return fetch( ajaxUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: body,
|
||||
} ).then( function ( r ) { return r.json(); } );
|
||||
}
|
||||
|
||||
// ─── Load folders ─────────────────────────────────────────
|
||||
function loadFolders() {
|
||||
if ( foldersCache ) {
|
||||
return Promise.resolve( foldersCache );
|
||||
}
|
||||
return ajax( 'mfp_get_folders' ).then( function ( res ) {
|
||||
if ( res.success ) {
|
||||
foldersCache = res.data.folders;
|
||||
return foldersCache;
|
||||
}
|
||||
return [];
|
||||
} );
|
||||
}
|
||||
|
||||
function flattenFolders( folders, depth ) {
|
||||
depth = depth || 0;
|
||||
var result = [];
|
||||
for ( var i = 0; i < folders.length; i++ ) {
|
||||
var folder = folders[ i ];
|
||||
var prefix = depth > 0 ? '\u2003'.repeat( depth ) + '\u2014 ' : '';
|
||||
result.push( {
|
||||
value: folder.id,
|
||||
label: prefix + folder.name,
|
||||
count: folder.count,
|
||||
} );
|
||||
if ( folder.children && folder.children.length > 0 ) {
|
||||
result = result.concat( flattenFolders( folder.children, depth + 1 ) );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─── Build a folder <select> element ──────────────────────
|
||||
function buildFolderSelect( onChange ) {
|
||||
var select = document.createElement( 'select' );
|
||||
select.className = 'mfp-modal-filter attachment-filters';
|
||||
|
||||
var defaultOpt = document.createElement( 'option' );
|
||||
defaultOpt.value = '0';
|
||||
defaultOpt.textContent = i18n.allMedia;
|
||||
select.appendChild( defaultOpt );
|
||||
|
||||
loadFolders().then( function ( folders ) {
|
||||
var flat = flattenFolders( folders );
|
||||
for ( var j = 0; j < flat.length; j++ ) {
|
||||
var opt = document.createElement( 'option' );
|
||||
opt.value = String( flat[ j ].value );
|
||||
opt.textContent = flat[ j ].label + ( flat[ j ].count > 0 ? ' (' + flat[ j ].count + ')' : '' );
|
||||
select.appendChild( opt );
|
||||
}
|
||||
if ( activeFolderId ) {
|
||||
select.value = String( activeFolderId );
|
||||
}
|
||||
} );
|
||||
|
||||
select.addEventListener( 'change', function () {
|
||||
var folderId = parseInt( select.value, 10 );
|
||||
activeFolderId = folderId || 0;
|
||||
if ( onChange ) onChange( folderId );
|
||||
// Sync all folder selects in the modal
|
||||
syncAllSelects( folderId );
|
||||
} );
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
// Keep all folder selects in sync (library toolbar + upload tab)
|
||||
var allSelects = [];
|
||||
function syncAllSelects( folderId ) {
|
||||
for ( var i = 0; i < allSelects.length; i++ ) {
|
||||
allSelects[ i ].value = String( folderId || 0 );
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Override AttachmentsBrowser toolbar ───────────────────
|
||||
var OriginalBrowser = wp.media.view.AttachmentsBrowser;
|
||||
|
||||
wp.media.view.AttachmentsBrowser = OriginalBrowser.extend( {
|
||||
createToolbar: function () {
|
||||
OriginalBrowser.prototype.createToolbar.call( this );
|
||||
|
||||
var toolbar = this.toolbar;
|
||||
var collection = this.collection;
|
||||
|
||||
var select = buildFolderSelect( function ( folderId ) {
|
||||
if ( folderId > 0 ) {
|
||||
collection.props.set( { media_folder: folderId } );
|
||||
} else {
|
||||
collection.props.set( { media_folder: 0 } );
|
||||
}
|
||||
} );
|
||||
|
||||
allSelects.push( select );
|
||||
|
||||
var FilterView = wp.media.View.extend( {
|
||||
tagName: 'div',
|
||||
className: 'mfp-modal-filter-wrap',
|
||||
render: function () {
|
||||
this.$el.append( select );
|
||||
return this;
|
||||
},
|
||||
} );
|
||||
|
||||
toolbar.set( 'mfpFolderFilter', new FilterView( {
|
||||
controller: this.controller,
|
||||
priority: -75,
|
||||
} ) );
|
||||
},
|
||||
} );
|
||||
|
||||
// ─── Add folder select to Upload tab ──────────────────────
|
||||
var OriginalUploaderInline = wp.media.view.UploaderInline;
|
||||
|
||||
wp.media.view.UploaderInline = OriginalUploaderInline.extend( {
|
||||
ready: function () {
|
||||
OriginalUploaderInline.prototype.ready.apply( this, arguments );
|
||||
|
||||
// Don't add twice
|
||||
if ( this.$el.find( '.mfp-upload-folder-wrap' ).length ) return;
|
||||
|
||||
var wrap = document.createElement( 'div' );
|
||||
wrap.className = 'mfp-upload-folder-wrap';
|
||||
|
||||
var label = document.createElement( 'label' );
|
||||
label.className = 'mfp-upload-folder-label';
|
||||
label.textContent = ( i18n.moveToFolder || 'Wyślij do folderu' ) + ': ';
|
||||
|
||||
var select = buildFolderSelect( function () {
|
||||
// Just updates activeFolderId via buildFolderSelect's change handler
|
||||
} );
|
||||
|
||||
allSelects.push( select );
|
||||
wrap.appendChild( label );
|
||||
wrap.appendChild( select );
|
||||
|
||||
// Insert before the upload area
|
||||
this.$el.prepend( wrap );
|
||||
},
|
||||
} );
|
||||
|
||||
// ─── Inject folder ID into plupload params ──────────────
|
||||
if ( wp.Uploader ) {
|
||||
var origUploaderInit = wp.Uploader.prototype.init;
|
||||
wp.Uploader.prototype.init = function () {
|
||||
origUploaderInit.apply( this, arguments );
|
||||
|
||||
var uploader = this.uploader;
|
||||
if ( ! uploader ) return;
|
||||
|
||||
uploader.bind( 'BeforeUpload', function () {
|
||||
if ( activeFolderId > 0 ) {
|
||||
uploader.settings.multipart_params.mfp_folder_id = activeFolderId;
|
||||
} else {
|
||||
delete uploader.settings.multipart_params.mfp_folder_id;
|
||||
}
|
||||
} );
|
||||
|
||||
uploader.bind( 'FileUploaded', function () {
|
||||
document.dispatchEvent( new CustomEvent( 'mfp-folder-changed' ) );
|
||||
|
||||
// Re-query the library so new file appears.
|
||||
// Don't use reset() — it clears the grid and shows "no items".
|
||||
// Instead, toggle the prop to force a fresh AJAX query.
|
||||
setTimeout( function () {
|
||||
if ( ! wp.media.frame ) return;
|
||||
try {
|
||||
var state = wp.media.frame.state();
|
||||
var lib = state && state.get( 'library' );
|
||||
if ( lib && lib.props ) {
|
||||
var folder = lib.props.get( 'media_folder' ) || 0;
|
||||
lib.props.unset( 'media_folder', { silent: true } );
|
||||
lib.props.set( { media_folder: folder } );
|
||||
}
|
||||
} catch ( e ) {}
|
||||
}, 800 );
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Sync on modal open/close ─────────────────────────────
|
||||
var origModalOpen = wp.media.view.Modal.prototype.open;
|
||||
wp.media.view.Modal.prototype.open = function () {
|
||||
var modal = this;
|
||||
origModalOpen.apply( this, arguments );
|
||||
|
||||
if ( ! modal._mfpCloseHooked ) {
|
||||
modal._mfpCloseHooked = true;
|
||||
var origClose = modal.close;
|
||||
modal.close = function () {
|
||||
foldersCache = null;
|
||||
activeFolderId = 0;
|
||||
allSelects = [];
|
||||
document.dispatchEvent( new CustomEvent( 'mfp-folder-changed' ) );
|
||||
return origClose.apply( this, arguments );
|
||||
};
|
||||
}
|
||||
};
|
||||
} )();
|
||||
Reference in New Issue
Block a user