Celebrate CodingBunny’s founder turning 45 - 45% OFF
The offer will end in
DAYS
HOURS
MINUTES
Enhance the WordPress media library

/**
* Snippet Name: Enhanced Media Library
* Snippet Author: coding-bunny.com
* Description: Enhance the WordPress media library.
* Version: 1.0.2
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action('admin_head', 'cbaml_styles');
add_action('admin_footer', 'cbaml_scripts');
add_action('wp_ajax_cbaml_save_rating', 'cbaml_save_rating');
add_action('wp_ajax_cbaml_save_label', 'cbaml_save_label');
add_action('wp_ajax_cbaml_save_thumbsize', 'cbaml_save_thumbsize');
add_action('wp_ajax_cbaml_save_theme', 'cbaml_save_theme');
add_action('wp_ajax_cbaml_get_media', 'cbaml_get_media');
add_action('wp_ajax_cbaml_delete_media', 'cbaml_delete_media');
function cbaml_styles() {
if (!current_user_can('upload_files')) return;
$screen = get_current_screen();
if ($screen && $screen->id === 'upload') {
?>
<style>
:root {
--bg1: #f7f8fa;
--bg2: #fff;
--bg3: #eceef0;
--text1: #23272f;
--text2: #7a7a7a;
--text3: #b0b0b0;
--border: #d7dadf;
--accent: #3858e9;
--danger: #fa5252;
--warning: #ffd43b;
--shadow1: rgba(0,0,0,0.04);
--shadow2: rgba(0,0,0,0.13);
}
[data-theme="dark"] {
--bg1: #16191c;
--bg2: #23272f;
--bg3: #262a33;
--text1: #fff;
--text2: #b0b0b0;
--text3: #888;
--border: #23272f;
--accent: #4a9eff;
--danger: #ff6b6b;
--warning: #ffe066;
--shadow1: rgba(0,0,0,0.13);
--shadow2: rgba(0,0,0,0.24);
}
.upload-php .wp-list-table.media,
.upload-php .tablenav,
.upload-php .wp-filter,
.upload-php .view-switch,
.upload-php .media-toolbar,
.upload-php .attachments-browser,
.upload-php .media-frame,
.upload-php .media-toolbar-secondary,
.upload-php .media-toolbar-primary,
.upload-php .media-frame-content,
.upload-php .media-sidebar {display:none!important;}
.cbaml-container {background:var(--bg1);border-radius:3px;padding:20px;margin:20px 0;box-shadow:0 2px 10px var(--shadow1);}
.cbaml-controls {background:var(--bg2);padding:15px 15px 10px 15px;border-radius:3px;margin-bottom:18px;border:1px solid var(--border);}
.cbaml-row {display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-bottom:12px;}
.cbaml-row:last-child {margin-bottom:0;}
.cbaml-group {display:flex;align-items:center;gap:8px;}
.cbaml-label {font-weight:600;color:var(--text1);font-size:14px;min-width:70px;}
.cbaml-theme {background:var(--bg3);border:none;color:var(--text1);padding:6px 14px;border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;transition:background .2s;display:flex;align-items:center;gap:5px;}
.cbaml-theme:hover {background:var(--accent);color:#fff;}
.cbaml-search {flex:1;min-width:180px;padding:9px 16px;border:1px solid var(--border);border-radius:3px;font-size:15px;background:var(--bg3);color:var(--text1);transition:border .2s;}
.cbaml-search:focus {outline:none;border-color:var(--accent);background:var(--bg2);}
.cbaml-slider {width:120px;margin:0 7px;height:5px;background:var(--border);border-radius:3px;-webkit-appearance:none;}
.cbaml-slider::-webkit-slider-thumb,.cbaml-slider::-moz-range-thumb {width:16px;height:16px;border-radius:2px;background:var(--accent);cursor:pointer;border:none;}
.cbaml-slidervalue {font-size:13px;color:var(--text2);min-width:45px;font-weight:600;background:var(--bg3);color:var(--text1);padding:2px 7px;border-radius:3px;}
.cbaml-filters {display:flex;gap:7px;flex-wrap:wrap;}
.cbaml-filter {padding:6px 13px;background:var(--bg3);border:1px solid var(--border);border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;color:var(--text1);transition:background .2s,color .2s,border .2s;}
.cbaml-filter.active,.cbaml-filter:focus {background:var(--accent);color:#fff;border-color:var(--accent);}
.cbaml-filter:hover:not(.active) {background:var(--border);}
.cbaml-labels {display:flex;gap:5px;flex-wrap:wrap;align-items:center;}
.cbaml-labelcolor {width:22px;height:22px;border-radius:3px;cursor:pointer;border:2px solid transparent;transition:border .2s,box-shadow .2s,background .2s;position:relative;flex-shrink:0;}
.cbaml-labelcolor.active,.cbaml-labelcolor:hover {border-color:var(--accent);box-shadow:0 1px 3px var(--shadow2);}
.cbaml-labelcolor.red {background:#e74c3c;}
.cbaml-labelcolor.blue {background:#3498db;}
.cbaml-labelcolor.green {background:#2ecc71;}
.cbaml-labelcolor.yellow {background:#f1c40f;}
.cbaml-labelcolor.purple {background:#9b59b6;}
.cbaml-labelcolor.orange {background:#e67e22;}
.cbaml-labelcolor.pink {background:#ff69b4;}
.cbaml-labelcolor.teal {background:#1abc9c;}
.cbaml-labelcolor.none {background:var(--bg2);border-color:var(--border);}
.cbaml-labelcolor.none::before {content:'×';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text3);font-weight:bold;font-size:15px;}
.cbaml-gallery {display:grid;gap:14px;margin-top:16px;transition:all 0.2s;}
.cbaml-list-view {display:block;}
.cbaml-list-table {width:100%;border-collapse:collapse;}
.cbaml-list-table th,.cbaml-list-table td {padding:8px 6px;font-size:13px;}
.cbaml-list-table th {background:var(--bg3);color:var(--text1);font-weight:600;text-align: left;}
.cbaml-list-table tr {border-bottom:1px solid var(--border);}
.cbaml-list-table td.cbaml-thumbcell img {height:40px;width:auto;}
.cbaml-list-table td.cbaml-list-actions {white-space:nowrap;}
.cbaml-view-toggle {margin-left:16px;border:none;background:var(--bg3);color:var(--text1);font-size:15px;padding:6px 14px;border-radius:3px;font-weight:600;cursor:pointer;transition:background .2s;}
.cbaml-view-toggle.active,.cbaml-view-toggle:focus {background:var(--accent);color:#fff;}
.cbaml-item {background:var(--bg2);border-radius:3px;padding:10px;box-shadow:0 2px 8px var(--shadow1);position:relative;cursor:pointer;border:1.5px solid transparent;transition:border .2s,box-shadow .2s,transform .18s;}
.cbaml-item:hover {border-color:var(--accent);box-shadow:0 4px 12px var(--shadow2);transform:translateY(-2px);}
.cbaml-item.selected {border-color:var(--accent);box-shadow:0 0 0 2px var(--accent);}
.cbaml-itemlabel {position:absolute;top:8px;right:8px;width:20px;height:20px;border-radius:2px;border:1.5px solid #fff;z-index:3;}
.cbaml-itemlabel.red {background:#e74c3c;}
.cbaml-itemlabel.blue {background:#3498db;}
.cbaml-itemlabel.green {background:#2ecc71;}
.cbaml-itemlabel.yellow {background:#f1c40f;}
.cbaml-itemlabel.purple {background:#9b59b6;}
.cbaml-itemlabel.orange {background:#e67e22;}
.cbaml-itemlabel.pink {background:#ff69b4;}
.cbaml-itemlabel.teal {background:#1abc9c;}
.cbaml-thumb {width:100%;object-fit:contain;border-radius:2px;margin-bottom:7px;background:var(--bg3);transition:transform .18s;box-shadow:none;}
.cbaml-item:hover .cbaml-thumb {transform:scale(1.01);}
.cbaml-filename {font-size:12px;font-weight:600;color:var(--text1);margin-bottom:6px;word-break:break-word;line-height:1.35;max-height:2.5em;overflow:hidden;}
.cbaml-meta {font-size:12px;color:var(--text2);margin-bottom:6px;}
.cbaml-ratingwrap {display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;padding:3px 3px 3px 5px;background:var(--bg3);border-radius:2px;}
.cbaml-stars {display:flex;gap:1px;align-items:center;}
.cbaml-star {cursor:pointer;color:var(--border);font-size:16px;transition:color .13s,transform .13s;user-select:none;}
.cbaml-star.filled,.cbaml-star.hover {color:var(--warning);}
.cbaml-star.remove-hover {color:var(--danger);}
.cbaml-star:hover,.cbaml-star.hover {transform:scale(1.12);}
.cbaml-star.remove-hover {transform:scale(1.16);}
.cbaml-remove {cursor:pointer;color:var(--danger);font-size:15px;font-weight:bold;margin-left:4px;padding:0 4px;border-radius:2px;background:transparent;border:none;transition:background .15s;display:flex;align-items:center;justify-content:center;}
.cbaml-remove:hover {background:var(--danger);color:#fff;}
.cbaml-ratingcount {font-size:11px;color:var(--text3);font-weight:600;padding:0 4px;border-radius:2px;}
.cbaml-labelselect {display:none;gap:3px;justify-content:center;margin-bottom:7px;}
.cbaml-item.selected .cbaml-labelselect,.cbaml-item:hover .cbaml-labelselect {display:flex;}
.cbaml-dot {width:14px;height:14px;border-radius:2px;cursor:pointer;border:1.5px solid transparent;transition:border .15s,box-shadow .15s;flex-shrink:0;position:relative;}
.cbaml-dot.active,.cbaml-dot:hover {border-color:var(--accent);box-shadow:0 1px 2px var(--shadow2);}
.cbaml-dot.red {background:#e74c3c;}
.cbaml-dot.blue {background:#3498db;}
.cbaml-dot.green {background:#2ecc71;}
.cbaml-dot.yellow {background:#f1c40f;}
.cbaml-dot.purple {background:#9b59b6;}
.cbaml-dot.orange {background:#e67e22;}
.cbaml-dot.pink {background:#ff69b4;}
.cbaml-dot.teal {background:#1abc9c;}
.cbaml-dot.none {background:var(--bg2);border-color:var(--border);}
.cbaml-dot.none::before {content:'×';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text3);font-weight:bold;font-size:11px;}
.cbaml-actions {display:flex;gap:5px;opacity:0;transition:opacity .13s;}
.cbaml-item:hover .cbaml-actions {opacity:1;}
.cbaml-action {flex:1;padding:4px 8px;font-size:12px;border:none;border-radius:2px;cursor:pointer;font-weight:600;background:var(--accent);color:#fff;transition:background .13s;}
.cbaml-action.delete {background:var(--danger);}
.cbaml-action:hover {filter:brightness(1.05);}
.cbaml-bulk {background:var(--accent);color:#fff;padding:13px 16px;border-radius:3px;margin-bottom:16px;display:none;align-items:center;justify-content:space-between;}
.cbaml-bulk.show {display:flex;}
.cbaml-bulkcontrols {display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.cbaml-bulkbtn {background:rgba(255,255,255,0.17);border:none;color:#fff;padding:5px 10px;border-radius:3px;cursor:pointer;font-size:13px;font-weight:600;transition:background .13s;}
.cbaml-bulkbtn:hover {background:rgba(255,255,255,0.24);}
.cbaml-stats {background:var(--bg2);padding:9px 15px;border-radius:3px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--text2);border:1px solid var(--border);}
.cbaml-statsleft {font-weight:600;color:var(--text1);}
.cbaml-statsright {color:var(--accent);font-weight:500;}
.cbaml-spinner {display:inline-block;width:32px;height:32px;border:4px solid var(--border);border-top:4px solid var(--accent);border-radius:50%;animation:spin 1s linear infinite;margin:40px auto;}
@keyframes spin {0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}
.cbaml-empty {text-align:center;color:var(--text2);padding:42px 12px;font-size:17px;}
.cbaml-emptyicon {font-size:42px;margin-bottom:12px;opacity:0.6;}
@media (max-width:1200px) {
.cbaml-row {flex-direction:column;align-items:stretch;}
.cbaml-search {min-width:auto;}
}
@media (max-width:768px) {
.cbaml-container,.cbaml-controls {padding:10px;}
.cbaml-filters,.cbaml-labels {justify-content:center;}
.cbaml-bulk {flex-direction:column;gap:10px;text-align:center;}
.cbaml-bulkcontrols {justify-content:center;}
}
</style>
<?php
}
}
function cbaml_scripts() {
if (!current_user_can('upload_files')) return;
$screen = get_current_screen();
if ($screen && $screen->id === 'upload') {
$user_id = get_current_user_id();
$saved_size = get_user_meta($user_id, 'cbaml_thumbsize', true);
$saved_theme = get_user_meta($user_id, 'cbaml_theme', true);
$thumbsize = $saved_size ? intval($saved_size) : 150;
$theme = ($saved_theme && intval($saved_theme) === 1) ? 'true' : 'false';
$ajaxurl = esc_url_raw(admin_url('admin-ajax.php'));
$nonce_media = wp_create_nonce('cbaml_media');
$nonce_size = wp_create_nonce('cbaml_size');
$nonce_theme = wp_create_nonce('cbaml_theme');
$nonce_rating = wp_create_nonce('cbaml_rating');
$nonce_label = wp_create_nonce('cbaml_label');
?>
<script>
jQuery(document).ready(function($) {
let selected = new Set();
let fRating = 'all', fLabel = 'all', fType = 'all', fDate = 'all', fSort = 'date_desc';
let items = [], filtered = [];
let thumbsize = <?php echo $thumbsize; ?>;
let theme = <?php echo $theme; ?>;
let page = 1, perPage = 30, more = false;
let view = 'grid';
window.cbamlItems = items;
function applyTheme() {
if(theme){
$('body').attr('data-theme','dark');
$('.cbaml-themeicon').text('☀️');
$('.cbaml-themetext').text('Light');
}else{
$('body').removeAttr('data-theme');
$('.cbaml-themeicon').text('🌙');
$('.cbaml-themetext').text('Dark');
}
}
function sanitize(str) {
return $('<div/>').text(str).html();
}
function getType(fn, mime) {
const ext = fn.split('.').pop().toLowerCase();
if (/^image\//.test(mime) || ['jpg','jpeg','png','gif','svg','webp','bmp'].includes(ext)) return 'image';
if (/^video\//.test(mime) || ['mp4','mov','avi','webm','mkv'].includes(ext)) return 'video';
if (/^audio\//.test(mime) || ['mp3','wav','ogg','aac'].includes(ext)) return 'audio';
if (ext === 'pdf') return 'pdf';
if (['doc','docx','odt','rtf'].includes(ext)) return 'doc';
return 'other';
}
function render() {
filtered = items.filter(item=>{
if(fRating!=='all'&&(parseInt(item.rating)||0)!==parseInt(fRating)) return false;
if(fLabel!=='all'){
const label = item.label||'';
if(fLabel==='none'&&label!=='')return false;
if(fLabel!=='none'&&label!==fLabel)return false;
}
if(fType!=='all'&&getType(item.filename,item.mime)!==fType)return false;
if(fDate!=='all'){
const d = new Date(item.raw_date), now = new Date();
if(fDate==='today'&&d.toDateString()!==now.toDateString())return false;
if(fDate==='7days'&&(now-d)/864e5>7)return false;
if(fDate==='30days'&&(now-d)/864e5>30)return false;
if(fDate==='year'&&(now-d)/864e5>365)return false;
}
const s = $('.cbaml-search').val().toLowerCase();
if(s&&!item.filename.toLowerCase().includes(s))return false;
return true;
});
if(fSort==='az')filtered.sort((a,b)=>a.filename.localeCompare(b.filename));
else if(fSort==='za')filtered.sort((a,b)=>b.filename.localeCompare(a.filename));
else if(fSort==='date_asc')filtered.sort((a,b)=>new Date(a.raw_date)-new Date(b.raw_date));
else filtered.sort((a,b)=>new Date(b.raw_date)-new Date(a.raw_date));
renderPage();
stats();
}
function renderPage() {
const g = $('.cbaml-gallery');
let start=0,end=page*perPage;
let show=filtered.slice(0,end);more=end<filtered.length;
if(!show.length){g.html('<div class="cbaml-empty"><div class="cbaml-emptyicon">🔍</div>No items found.</div>');$('.cbaml-loadmore').hide();return;}
if(view==='list'){ renderList(show); }
else { renderGrid(show); }
if(more) $('.cbaml-loadmore').show();else $('.cbaml-loadmore').hide();
}
function renderGrid(show) {
let html='';
show.forEach(item=>{
const rating=parseInt(item.rating)||0,label=item.label||'',stars=starHtml(rating),dots=dotHtml(label);
const sel=selected.has(item.id)?'selected':'';
html+=`
<div class="cbaml-item ${sel}" data-id="${sanitize(item.id)}" data-rating="${sanitize(rating)}" data-label="${sanitize(label)}">
${label?`<div class="cbaml-itemlabel ${sanitize(label)}"></div>`:''}
<img src="${sanitize(item.thumbnail)}" alt="${sanitize(item.filename)}" class="cbaml-thumb" loading="lazy" style="height:${thumbsize}px;">
<div class="cbaml-filename" title="${sanitize(item.filename)}">${sanitize(item.filename)}</div>
<div class="cbaml-meta">${sanitize(item.dimensions)} • ${sanitize(item.filesize)} • ${sanitize(item.date)}</div>
<div class="cbaml-ratingwrap">
<div class="cbaml-stars" data-id="${sanitize(item.id)}">
${stars}
<button type="button" class="cbaml-remove" data-id="${sanitize(item.id)}" title="Remove rating">×</button>
</div>
<span class="cbaml-ratingcount">${sanitize(rating)}/5</span>
</div>
<div class="cbaml-labelselect" data-id="${sanitize(item.id)}">${dots}</div>
<div class="cbaml-actions">
<button class="cbaml-action edit" data-id="${sanitize(item.id)}">Edit</button>
<button class="cbaml-action delete" data-id="${sanitize(item.id)}">Delete</button>
</div>
</div>`;
});
$('.cbaml-gallery').removeClass('cbaml-list-view').html(html);
}
function renderList(show) {
let html = `<table class="cbaml-list-table"><thead>
<tr>
<th>Thumb</th>
<th>Filename</th>
<th>Type</th>
<th>Dimensions</th>
<th>Size</th>
<th>Date</th>
<th>Rating</th>
<th>Label</th>
<th>Actions</th>
</tr></thead><tbody>`;
show.forEach(item=>{
const rating=parseInt(item.rating)||0,label=item.label||'';
let stars = '';
for(let i=1;i<=5;i++)stars+=`<span class="cbaml-star${i<=rating?' filled':''}" data-rating="${i}" style="font-size:14px;">★</span>`;
let dot = label ? `<span class="cbaml-dot ${sanitize(label)} active"></span>` : `<span class="cbaml-dot none active"></span>`;
html+=`
<tr class="cbaml-list-row" data-id="${sanitize(item.id)}" data-rating="${sanitize(rating)}" data-label="${sanitize(label)}">
<td class="cbaml-thumbcell"><img src="${sanitize(item.thumbnail)}" alt="${sanitize(item.filename)}" style="height:40px;"></td>
<td>${sanitize(item.filename)}</td>
<td>${sanitize(getType(item.filename,item.mime))}</td>
<td>${sanitize(item.dimensions)}</td>
<td>${sanitize(item.filesize)}</td>
<td>${sanitize(item.date)}</td>
<td>
<span class="cbaml-stars" data-id="${sanitize(item.id)}">${stars}</span>
<button type="button" class="cbaml-remove" data-id="${sanitize(item.id)}" title="Remove rating" style="margin-left:5px;">×</button>
</td>
<td>
<div class="cbaml-labelselect" data-id="${sanitize(item.id)}" style="display:inline-flex;gap:2px;vertical-align:middle;">
${dotHtml(label)}
</div>
</td>
<td class="cbaml-list-actions">
<button class="cbaml-action edit" data-id="${sanitize(item.id)}">Edit</button>
<button class="cbaml-action delete" data-id="${sanitize(item.id)}">Delete</button>
</td>
</tr>`;
});
html+='</tbody></table>';
$('.cbaml-gallery').addClass('cbaml-list-view').html(html);
}
function starHtml(rating) {
let s='';for(let i=1;i<=5;i++){const f=i<=rating?'filled':'';s+=`<span class="cbaml-star ${f}" data-rating="${i}">★</span>`;}return s;
}
function dotHtml(active) {
const colors=['red','blue','green','yellow','purple','orange','pink','teal'];
let d = `<div class="cbaml-dot none${active===''?' active':''}" data-label="" title="No label"></div>`;
colors.forEach(c=>{d+=`<div class="cbaml-dot ${c}${active===c?' active':''}" data-label="${c}" title="${c.charAt(0).toUpperCase()+c.slice(1)}"></div>`;});
return d;
}
function stats() {
const total=items.length,visible=$('.cbaml-item,.cbaml-list-row').length,sel=selected.size;
$('.cbaml-total').text(`${visible} of ${total} items`);
$('.cbaml-statsright').text(sel>0?`${sel} selected`:'');
}
function grid() {
const min=Math.max(thumbsize,120);
$('.cbaml-gallery').css('grid-template-columns',`repeat(auto-fill,minmax(${min}px,1fr))`);
}
function load() {
$.ajax({
url: '<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_get_media',nonce:'<?php echo $nonce_media; ?>'},
success: function(r){
if(r.success){items=r.data;window.cbamlItems=items;page=1;render();}
},
error:function(){$('.cbaml-gallery').html('<div class="cbaml-empty"><div class="cbaml-emptyicon">😞</div>Error loading media.</div>');}
});
}
function saveThumb() {
$.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_thumbsize',size:thumbsize,nonce:'<?php echo $nonce_size; ?>'}});
}
function saveTheme() {
$.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_theme',theme: theme ? 'true' : 'false',nonce:'<?php echo $nonce_theme; ?>'}});
}
function saveRating(id,r) {
$.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_rating',item_id:id,rating:r,nonce:'<?php echo $nonce_rating; ?>'}});
}
function saveLabel(id,l) {
$.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_save_label',item_id:id,label:l,nonce:'<?php echo $nonce_label; ?>'}});
}
function bind() {
$('.cbaml-theme').on('click',function(){
theme=!theme;
applyTheme();
saveTheme();
});
$('.cbaml-slider').on('input',function(){
thumbsize=parseInt($(this).val());
$('.cbaml-slidervalue').text(thumbsize+'px');
$('.cbaml-thumb').css('height',thumbsize+'px');
grid();
}).on('change',saveThumb);
$(document).on('click','.cbaml-remove',function(e){
e.stopPropagation();const id=$(this).data('id');setRating(id,0);
});
$(document).on('click','.cbaml-star',function(e){
e.stopPropagation();
const $s=$(this),r=$s.data('rating'),id=$s.closest('.cbaml-stars').data('id');
const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`);
const curr=parseInt($item.data('rating'))||0;
setRating(id,curr===r?0:r);
});
$(document).on('mouseenter','.cbaml-star',function(){
const $t=$(this),r=$t.data('rating'),$s=$t.closest('.cbaml-stars').find('.cbaml-star'),id=$t.closest('.cbaml-stars').data('id');
const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`);
const curr=parseInt($item.data('rating'))||0;
$s.removeClass('hover remove-hover');
if(curr===r){$t.addClass('remove-hover');}else{$s.slice(0,r).addClass('hover');}
});
$(document).on('mouseleave','.cbaml-stars',function(){$(this).find('.cbaml-star').removeClass('hover remove-hover');});
$(document).on('click','.cbaml-dot',function(e){
e.stopPropagation();
const $d=$(this),label=$d.data('label'),id=$d.closest('.cbaml-labelselect').data('id');
setLabel(id,label);
});
$(document).on('click','.cbaml-item',function(e){
if($(e.target).is('.cbaml-star,.cbaml-action,.cbaml-dot,.cbaml-remove'))return;
const id=parseInt($(this).data('id'));
if(e.ctrlKey||e.metaKey){toggle(id);}
else{clearSel();select(id);}
});
$('.cbaml-search').on('input',debounce(function(){render();},300));
$('.cbaml-sort').on('change',function(){fSort=$(this).val();render();});
$('.cbaml-type').on('change',function(){fType=$(this).val();render();});
$('.cbaml-date').on('change',function(){fDate=$(this).val();render();});
$(document).on('click','.cbaml-filter[data-filter]',function(){
$('.cbaml-filter[data-filter]').removeClass('active');$(this).addClass('active');
fRating=$(this).data('filter');render();
});
$(document).on('click','[data-label-filter]',function(){
$('[data-label-filter]').removeClass('active');$(this).addClass('active');
fLabel=$(this).data('label-filter');render();
});
$(document).on('click','.cbaml-bulkbtn.rate',function(){
const r=$(this).data('rating');selected.forEach(id=>setRating(id,r));
});
$(document).on('click','.cbaml-bulklabel',function(){
const l=$(this).data('label');selected.forEach(id=>setLabel(id,l));
});
$(document).on('click','.cbaml-bulkbtn.deselect',function(){clearSel();});
$(document).on('keydown',function(e){
if(e.ctrlKey&&e.key==='a'){e.preventDefault();selectAll();}
if(e.key>='1'&&e.key<='5'&&selected.size>0)selected.forEach(id=>setRating(id,parseInt(e.key)));
if(e.key==='0'&&selected.size>0)selected.forEach(id=>setRating(id,0));
});
$(document).on('click','.cbaml-action.edit',function(e){
e.stopPropagation();
const id=$(this).data('id');
window.open('<?php echo esc_url(admin_url('post.php')); ?>?post='+encodeURIComponent(id)+'&action=edit','_blank');
});
$(document).on('click','.cbaml-action.delete',function(e){
e.stopPropagation();const id=$(this).data('id');
if(confirm('Are you sure?')){
$.ajax({url:'<?php echo $ajaxurl; ?>',type:'POST',data:{action:'cbaml_delete_media',nonce:'<?php echo $nonce_media; ?>',item_id:id},
success:function(r){
if(r.success){items=items.filter(i=>i.id!=id);selected.delete(id);render();}
else alert(r.data||'Delete error');
}
});
}
});
$('.cbaml-loadmore').on('click',function(){page+=1;renderPage();});
$('.cbaml-view-toggle').on('click',function(){
$('.cbaml-view-toggle').removeClass('active');
$(this).addClass('active');
view=$(this).data('view');
page=1;
render();
});
}
function setRating(id,r){
const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`),$s=$item.find('.cbaml-star');
$s.removeClass('filled');if(r>0)$s.slice(0,r).addClass('filled');
$item.find('.cbaml-ratingcount').text(`${r}/5`);
$item.attr('data-rating',r);
const it=items.find(i=>i.id==id);if(it)it.rating=r;
saveRating(id,r);
}
function setLabel(id,l){
const $item=view==='list'?$('.cbaml-list-row[data-id="'+id+'"]'):$(`.cbaml-item[data-id="${id}"]`),$d=$item.find('.cbaml-dot');
$d.removeClass('active');$d.filter(`[data-label="${l}"]`).addClass('active');
$item.attr('data-label',l);
$item.find('.cbaml-itemlabel').remove();
if(l&&!view==='list')$item.prepend(`<div class="cbaml-itemlabel ${l}"></div>`);
const it=items.find(i=>i.id==id);if(it)it.label=l;
saveLabel(id,l);
}
function toggle(id){if(selected.has(id)){selected.delete(id);$(`.cbaml-item[data-id="${id}"]`).removeClass('selected');}else{selected.add(id);$(`.cbaml-item[data-id="${id}"]`).addClass('selected');}bulk();}
function select(id){selected.add(id);$(`.cbaml-item[data-id="${id}"]`).addClass('selected');bulk();}
function clearSel(){selected.clear();$('.cbaml-item').removeClass('selected');bulk();}
function selectAll(){$('.cbaml-item:visible').each(function(){const id=parseInt($(this).data('id'));selected.add(id);$(this).addClass('selected');});bulk();}
function bulk(){const c=selected.size;if(c>0){$('.cbaml-bulk').addClass('show');$('.cbaml-bulkcount').text(`${c} selected`);}else{$('.cbaml-bulk').removeClass('show');}stats();}
function debounce(f,w){let t;return function(...a){const l=()=>{clearTimeout(t);f(...a);};clearTimeout(t);t=setTimeout(l,w);};}
function layout() {
const html=`
<div class="cbaml-container">
<div style="text-align:right;margin-bottom:8px;">
<button class="cbaml-view-toggle active" data-view="grid" title="Grid view">▥</button>
<button class="cbaml-view-toggle" data-view="list" title="List view">☰</button>
</div>
<div class="cbaml-stats">
<div class="cbaml-statsleft"><span class="cbaml-total">Loading...</span></div>
<div class="cbaml-statsright"></div>
</div>
<div class="cbaml-bulk">
<span class="cbaml-bulkcount">0 selected</span>
<div class="cbaml-bulkcontrols">
<button class="cbaml-bulkbtn rate" data-rating="1">★</button>
<button class="cbaml-bulkbtn rate" data-rating="2">★★</button>
<button class="cbaml-bulkbtn rate" data-rating="3">★★★</button>
<button class="cbaml-bulkbtn rate" data-rating="4">★★★★</button>
<button class="cbaml-bulkbtn rate" data-rating="5">★★★★★</button>
<button class="cbaml-bulkbtn rate" data-rating="0">Remove Rating</button>
<div class="cbaml-labels">
<div class="cbaml-labelcolor red cbaml-bulklabel" data-label="red" title="Red"></div>
<div class="cbaml-labelcolor blue cbaml-bulklabel" data-label="blue" title="Blue"></div>
<div class="cbaml-labelcolor green cbaml-bulklabel" data-label="green" title="Green"></div>
<div class="cbaml-labelcolor yellow cbaml-bulklabel" data-label="yellow" title="Yellow"></div>
<div class="cbaml-labelcolor purple cbaml-bulklabel" data-label="purple" title="Purple"></div>
<div class="cbaml-labelcolor orange cbaml-bulklabel" data-label="orange" title="Orange"></div>
<div class="cbaml-labelcolor pink cbaml-bulklabel" data-label="pink" title="Pink"></div>
<div class="cbaml-labelcolor teal cbaml-bulklabel" data-label="teal" title="Teal"></div>
<div class="cbaml-labelcolor none cbaml-bulklabel" data-label="" title="Remove label"></div>
</div>
<button class="cbaml-bulkbtn deselect">Deselect</button>
</div>
</div>
<div class="cbaml-controls">
<div class="cbaml-row">
<input type="text" class="cbaml-search" placeholder="🔍 Search filename...">
<div class="cbaml-group"><span class="cbaml-label">Sort:</span>
<select class="cbaml-sort">
<option value="date_desc">Newest</option>
<option value="date_asc">Oldest</option>
<option value="az">A → Z</option>
<option value="za">Z → A</option>
</select>
</div>
<div class="cbaml-group"><span class="cbaml-label">Type:</span>
<select class="cbaml-type">
<option value="all">All</option>
<option value="image">Images</option>
<option value="video">Videos</option>
<option value="audio">Audio</option>
<option value="pdf">PDF</option>
<option value="doc">Doc</option>
<option value="other">Other</option>
</select>
</div>
<div class="cbaml-group"><span class="cbaml-label">Date:</span>
<select class="cbaml-date">
<option value="all">All</option>
<option value="today">Today</option>
<option value="7days">Last 7 days</option>
<option value="30days">Last 30 days</option>
<option value="year">Last year</option>
</select>
</div>
<div class="cbaml-group"><span class="cbaml-label">Size:</span>
<input type="range" class="cbaml-slider" min="100" max="300" value="${thumbsize}">
<span class="cbaml-slidervalue">${thumbsize}px</span>
</div>
<button class="cbaml-theme">
<span class="cbaml-themeicon">${theme?'☀️':'🌙'}</span>
<span class="cbaml-themetext">${theme?'Light':'Dark'}</span>
</button>
</div>
<div class="cbaml-row">
<div class="cbaml-group">
<span class="cbaml-label">Rating:</span>
<div class="cbaml-filters">
<button class="cbaml-filter active" data-filter="all">All</button>
<button class="cbaml-filter" data-filter="5">★★★★★</button>
<button class="cbaml-filter" data-filter="4">★★★★</button>
<button class="cbaml-filter" data-filter="3">★★★</button>
<button class="cbaml-filter" data-filter="2">★★</button>
<button class="cbaml-filter" data-filter="1">★</button>
<button class="cbaml-filter" data-filter="0">No rating</button>
</div>
</div>
</div>
<div class="cbaml-row">
<div class="cbaml-group">
<span class="cbaml-label">Labels:</span>
<div class="cbaml-filters">
<button class="cbaml-filter active" data-label-filter="all">All</button>
</div>
<div class="cbaml-labels">
<div class="cbaml-labelcolor red" data-label-filter="red" title="Red"></div>
<div class="cbaml-labelcolor blue" data-label-filter="blue" title="Blue"></div>
<div class="cbaml-labelcolor green" data-label-filter="green" title="Green"></div>
<div class="cbaml-labelcolor yellow" data-label-filter="yellow" title="Yellow"></div>
<div class="cbaml-labelcolor purple" data-label-filter="purple" title="Purple"></div>
<div class="cbaml-labelcolor orange" data-label-filter="orange" title="Orange"></div>
<div class="cbaml-labelcolor pink" data-label-filter="pink" title="Pink"></div>
<div class="cbaml-labelcolor teal" data-label-filter="teal" title="Teal"></div>
<div class="cbaml-labelcolor none" data-label-filter="none" title="No label"></div>
</div>
</div>
</div>
</div>
<div class="cbaml-gallery"><div class="cbaml-spinner"></div></div>
<div style="text-align:center;margin:18px 0 0 0;">
<button class="cbaml-loadmore" style="display:none;padding:8px 24px;border:none;border-radius:3px;background:var(--accent);color:#fff;font-size:14px;font-weight:400;cursor:pointer;">LOAD MORE...</button>
</div>
</div>`;
$('.wrap h1').after(html);
}
layout();bind();load();grid();applyTheme();
});
</script>
<?php
}
}
function cbaml_check_permissions() {
if (!current_user_can('upload_files')) {
wp_send_json_error('Permission denied', 403);
exit;
}
}
function cbaml_get_media() {
cbaml_check_permissions();
check_ajax_referer('cbaml_media', 'nonce');
$args = array(
'post_type' => 'attachment',
'post_status' => 'any',
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'DESC'
);
$attachments = get_posts($args);
$items = array();
foreach ($attachments as $a) {
$fp = get_attached_file($a->ID);
if (!$fp || !file_exists($fp)) continue;
$m = wp_get_attachment_metadata($a->ID);
$rating = get_post_meta($a->ID, '_media_rating', true) ?: 0;
$label = get_post_meta($a->ID, '_media_label', true) ?: '';
$size = filesize($fp);
$thumb = wp_get_attachment_image_url($a->ID, 'medium') ?: wp_get_attachment_url($a->ID);
$orig = wp_get_attachment_url($a->ID);
$mime = get_post_mime_type($a->ID);
$items[] = array(
'id' => (int)$a->ID,
'filename' => esc_html(basename($fp)),
'thumbnail' => esc_url_raw($thumb),
'original_url' => esc_url_raw($orig),
'dimensions' => isset($m['width']) ? ((int)$m['width'].'×'.(int)$m['height']) : 'N/A',
'filesize' => size_format($size),
'date' => esc_html(date_i18n('Y-m-d', strtotime($a->post_date))),
'raw_date' => esc_html($a->post_date),
'rating' => (int)$rating,
'label' => sanitize_text_field($label),
'mime' => sanitize_mime_type($mime),
);
}
wp_send_json_success($items);
}
function cbaml_save_rating() {
cbaml_check_permissions();
check_ajax_referer('cbaml_rating', 'nonce');
$id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
$r = isset($_POST['rating']) ? intval($_POST['rating']) : 0;
if ($id && $r >= 0 && $r <= 5) {
update_post_meta($id, '_media_rating', $r);
wp_send_json_success();
}
wp_send_json_error('Invalid request');
}
function cbaml_save_label() {
cbaml_check_permissions();
check_ajax_referer('cbaml_label', 'nonce');
$id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
$l = isset($_POST['label']) ? sanitize_text_field($_POST['label']) : '';
$ok = ['red','blue','green','yellow','purple','orange','pink','teal',''];
if ($id && in_array($l, $ok, true)) {
update_post_meta($id, '_media_label', $l);
wp_send_json_success();
}
wp_send_json_error('Invalid request');
}
function cbaml_save_thumbsize() {
cbaml_check_permissions();
check_ajax_referer('cbaml_size', 'nonce');
$size = isset($_POST['size']) ? intval($_POST['size']) : 0;
$user = get_current_user_id();
if ($size >= 100 && $size <= 300 && $user) {
update_user_meta($user, 'cbaml_thumbsize', $size);
wp_send_json_success();
}
wp_send_json_error('Invalid request');
}
function cbaml_save_theme() {
cbaml_check_permissions();
check_ajax_referer('cbaml_theme', 'nonce');
$theme = (isset($_POST['theme']) && $_POST['theme'] === 'true') ? 1 : 0;
$user = get_current_user_id();
if ($user) {
update_user_meta($user, 'cbaml_theme', $theme);
wp_send_json_success();
}
wp_send_json_error('Invalid request');
}
function cbaml_delete_media() {
cbaml_check_permissions();
check_ajax_referer('cbaml_media', 'nonce');
$id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
if ($id && current_user_can('delete_post', $id)) {
$deleted = wp_delete_attachment($id, true);
if ($deleted) {
wp_send_json_success();
}
wp_send_json_error('Delete failed');
}
wp_send_json_error('Permission denied');
}
add_filter('attachment_fields_to_edit', function($fields, $post) {
if (!current_user_can('upload_files')) return $fields;
$rating = get_post_meta($post->ID, '_media_rating', true) ?: 0;
$label = get_post_meta($post->ID, '_media_label', true) ?: '';
$fields['media_rating'] = array(
'label' => 'Rating',
'input' => 'html',
'html' => '<select name="attachments[' . $post->ID . '][media_rating]">
<option value="0"' . selected($rating, 0, false) . '>None</option>
<option value="1"' . selected($rating, 1, false) . '>★ (1/5)</option>
<option value="2"' . selected($rating, 2, false) . '>★★ (2/5)</option>
<option value="3"' . selected($rating, 3, false) . '>★★★ (3/5)</option>
<option value="4"' . selected($rating, 4, false) . '>★★★★ (4/5)</option>
<option value="5"' . selected($rating, 5, false) . '>★★★★★ (5/5)</option>
</select>'
);
$fields['media_label'] = array(
'label' => 'Label',
'input' => 'html',
'html' => '<select name="attachments[' . $post->ID . '][media_label]">
<option value=""' . selected($label, '', false) . '>None</option>
<option value="red"' . selected($label, 'red', false) . '>Red</option>
<option value="blue"' . selected($label, 'blue', false) . '>Blue</option>
<option value="green"' . selected($label, 'green', false) . '>Green</option>
<option value="yellow"' . selected($label, 'yellow', false) . '>Yellow</option>
<option value="purple"' . selected($label, 'purple', false) . '>Purple</option>
<option value="orange"' . selected($label, 'orange', false) . '>Orange</option>
<option value="pink"' . selected($label, 'pink', false) . '>Pink</option>
<option value="teal"' . selected($label, 'teal', false) . '>Teal</option>
</select>'
);
return $fields;
}, 10, 2);
add_filter('attachment_fields_to_save', function($post, $att) {
if (!current_user_can('upload_files')) return $post;
if (isset($att['media_rating'])) update_post_meta($post['ID'], '_media_rating', intval($att['media_rating']));
if (isset($att['media_label'])) update_post_meta($post['ID'], '_media_label', sanitize_text_field($att['media_label']));
return $post;
}, 10, 2);
?>