Plugin Directory

Changeset 3789757


Ignore:
Timestamp:
11/07/2026 02:39:22 PM (less than one hour ago)
Author:
RobWord
Message:

Release 0.1.15 — Tieraxo 0.1.15: Optimiser scans of large libraries no longer freeze the page, and rescans only read new or changed images. Pro adds a Convert button to the Media Library toolbar.

Location:
tieraxo-asset-booster
Files:
10 edited
35 copied

Legend:

Unmodified
Added
Removed
  • tieraxo-asset-booster/tags/0.1.15/changelog.txt

    r3787150 r3789757  
    1 Tieraxo Asset Booster: changelog for versions 0.1.3 and earlier.
     1Tieraxo Asset Booster: changelog for versions 0.1.5 and earlier.
    22The latest versions are in readme.txt.
     3
     4= 0.1.5 =
     5* Fixed: image dimensions in the Swap panel and Upload Triage could show a long decimal (for example "1791.818 × 1123.636 px"), because the browser sometimes reports a fractional size for a loaded image. They are now rounded to whole pixels.
     6* Fixed: activating a Pro licence failed, because the licence and download service moved from robsnow.eu (no longer online) to robart-illustration.eu. Tieraxo now uses the new address, and the readme, Upgrade page and sidebar links point there too.
     7* Fixed: in the "Add media" window, Tieraxo's folder controls overlapped WordPress's own toolbar at the bottom of the window. They now sit in one tidy row beside it.
     8
     9= 0.1.4 =
     10* Changed: the folder allowance and the analytics scan size are now the same for every install and are not tied to any tier or licence. Both are generous usability defaults and can be raised or removed with the `tiax_folder_cap` and `tiax_fa_scan_cap` filters. No folder is ever locked. Pro adds separate tools (Optimiser, cleanup, Upload Triage and more); it does not lift these limits.
     11* Fixed: corrected the Terms and Privacy links in the readme.
    312
    413= 0.1.3 =
  • tieraxo-asset-booster/tags/0.1.15/includes/families/optimise/optimise.css

    r3712333 r3789757  
    3232.tiax-opt-results { margin-top: 16px; }
    3333.tiax-opt-listhead { margin: 6px 0 8px; }
     34.tiax-opt-fresh-lbl { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #50575e; }
    3435.tiax-opt-samplenote { margin: 6px 0 0; font-size: 12px; color: #50575e; font-style: italic; }
    3536.tiax-opt-alphawarn-text { color: #8a5a00; }
  • tieraxo-asset-booster/tags/0.1.15/includes/families/optimise/optimise.js

    r3777573 r3789757  
    33   Global settings (format / quality / max-width / strip) with per-row overrides. Batched scan →
    44   review rows (thumbnail, name, current size, per-row format + quality dropdowns) → run the shared
    5    engine per selected image with its EFFECTIVE settings (global default unless the row overrides). */
     5   engine per selected image with its EFFECTIVE settings (global default unless the row overrides).
     6
     7   The scanned list lives in memory (ITEMS), not in the page. Filters, Select all and the run all
     8   work on that list; only the rows that pass the filters are drawn, a chunk at a time as the list
     9   scrolls. So a library of thousands of images never builds thousands of rows at once (which froze
     10   the browser at the end of big scans). */
    611( function ( $ ) {
    712        'use strict';
    … …  
    1015        var I18N = CFG.i18n || {};
    1116        var FORMATS = CFG.formats || [];
     17
     18        var BATCH = 100;   // images per scan request (the server caps it at 100 too).
     19        var CHUNK = 150;   // rows drawn per chunk.
     20
     21        var ITEMS   = [];  // every scanned image: { id, name, thumb, size, bytes, w, fmt, alpha, f, q, picked, est }
     22        var VISIBLE = [];  // the ITEMS that pass the filters, in list order.
     23        var shown   = 0;   // how many of VISIBLE are drawn so far.
    1224
    1325        function globalFormat()  { return $( '.tiax-opt-format-global' ).val() || ''; }
    … …  
    1729
    1830        // A per-row format <select>: "Default" (follow global) + each writable format + Keep.
    19         function formatSelect() {
    20                 var opts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
    21                 opts += '<option value="__keep">' + esc( I18N.keepFmt || 'Keep format' ) + '</option>';
    22                 FORMATS.forEach( function ( f ) { opts += '<option value="' + f + '">' + f.toUpperCase() + '</option>'; } );
    23                 return '<select class="tiax-opt-row-format">' + opts + '</select>';
     31        var formatOpts = null, qualityOpts = null;
     32        function formatSelect( val ) {
     33                if ( null === formatOpts ) {
     34                        formatOpts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
     35                        formatOpts += '<option value="__keep">' + esc( I18N.keepFmt || 'Keep format' ) + '</option>';
     36                        FORMATS.forEach( function ( f ) { formatOpts += '<option value="' + esc( f ) + '">' + esc( f.toUpperCase() ) + '</option>'; } );
     37                }
     38                return $( '<select class="tiax-opt-row-format">' + formatOpts + '</select>' ).val( val || '' );
    2439        }
    2540        // A per-row quality <select>: "Default" + a few standard steps.
    26         function qualitySelect() {
    27                 var vals = [ 100, 92, 85, 82, 75, 65, 55, 40 ];
    28                 var opts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
    29                 vals.forEach( function ( v ) { opts += '<option value="' + v + '">' + v + '</option>'; } );
    30                 return '<select class="tiax-opt-row-quality">' + opts + '</select>';
    31         }
    32 
    33         // Effective settings for one row: override wins, else global.
    34         function rowSettings( $row ) {
    35                 var fSel = $row.find( '.tiax-opt-row-format' ).val();
    36                 var qSel = $row.find( '.tiax-opt-row-quality' ).val();
     41        function qualitySelect( val ) {
     42                if ( null === qualityOpts ) {
     43                        qualityOpts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
     44                        [ 100, 92, 85, 82, 75, 65, 55, 40 ].forEach( function ( v ) { qualityOpts += '<option value="' + v + '">' + v + '</option>'; } );
     45                }
     46                return $( '<select class="tiax-opt-row-quality">' + qualityOpts + '</select>' ).val( val || '' );
     47        }
     48
     49        // Effective settings for one image: its override wins, else global.
     50        function itemSettings( it ) {
    3751                var format;
    38                 if ( fSel === '__keep' ) { format = ''; }          // explicitly keep source format
    39                 else if ( fSel ) { format = fSel; }                // explicit target
    40                 else { format = globalFormat(); }                  // follow global
    41                 var quality = qSel ? parseInt( qSel, 10 ) : globalQuality();
    42                 return { id: parseInt( $row.find( '.tiax-opt-pick' ).data( 'id' ), 10 ), format: format, quality: quality };
    43         }
    44 
    45         function pickedRows() { return $( '.tiax-opt-pick:checked' ).closest( 'li.tiax-opt-item' ); }
    46         function updateSelected() {
    47                 var n = pickedRows().length;
    48                 // Only VISIBLE rows count toward "all" — filtered-out rows are excluded from the run.
    49                 var total = $( '.tiax-opt-pick:visible' ).length;
    50                 var acked = $( '.tiax-opt-ack' ).is( ':checked' );
    51                 $( '.tiax-opt-go' ).prop( 'disabled', ! ( n > 0 && acked ) ).text( ( I18N.runSel || 'Optimise %n selected' ).replace( '%n', n ) );
    52                 $( '.tiax-opt-all' ).prop( 'checked', total > 0 && n === total );
    53         }
    54 
    55         // Return the whole panel to its clean pre-scan state: hide the progress bar + results
    56         // block, empty the list, zero the counters, clear the run status and untick the ack.
    57         // (The completion confirmation lives in .tiax-opt-donemsg, OUTSIDE results, so it stays.)
    58         function resetPanel() {
    59                 var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', true );
    60                 $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
    61                 $prog.find( '.tiax-fix-progress__label' ).text( '' );
    62                 $( '.tiax-opt-results' ).prop( 'hidden', true );
    63                 $( '.tiax-opt-sample' ).empty();
    64                 $( '.tiax-opt-count' ).text( '0' );
    65                 $( '.tiax-opt-total' ).text( '0 B' );
    66                 $( '.tiax-opt-filters__count' ).text( '' );
    67                 $( '.tiax-opt-nofilter-note, .tiax-opt-alphawarn' ).prop( 'hidden', true );
    68                 $( '.tiax-opt-runstatus' ).text( '' );
    69                 $( '.tiax-opt-ack' ).prop( 'checked', false );
    70                 $( '.tiax-opt-all' ).prop( 'checked', false );
    71                 $( '.tiax-opt-go' ).prop( 'disabled', true );
    72         }
    73 
    74         function renderResults( d ) {
    75                 $( '.tiax-opt-donemsg' ).prop( 'hidden', true ); // a fresh scan clears the last run's confirmation
    76                 var $r = $( '.tiax-opt-results' ).prop( 'hidden', false );
    77                 $( '.tiax-opt-count' ).text( d.count || 0 );
    78                 $( '.tiax-opt-total' ).text( d.human || '0 B' );
    79                 var samples = d.samples || [];
    80                 var $s = $( '.tiax-opt-sample' ).empty();
    81                 samples.forEach( function ( it ) {
    82                         var $li = $( '<li class="tiax-opt-item">' );
    83                         $( '<input type="checkbox" class="tiax-opt-pick">' ).attr( 'data-id', it.id ).appendTo( $li );
    84                         if ( it.thumb ) { $( '<img class="tiax-unused-thumb" alt="" loading="lazy">' ).attr( 'src', it.thumb ).appendTo( $li ); }
    85                         else { $li.append( '<span class="tiax-unused-thumb tiax-unused-thumb--none" aria-hidden="true"></span>' ); }
    86                         $( '<span class="tiax-opt-name">' ).text( it.name ).appendTo( $li );
    87                         $( '<span class="tiax-opt-cur">' ).text( ( it.fmt ? it.fmt.toUpperCase() + ' · ' : '' ) + ( it.size || '' ) ).appendTo( $li );
    88                         $( '<span class="tiax-opt-est">' ).appendTo( $li );
    89                         $( formatSelect() ).appendTo( $li );
    90                         $( qualitySelect() ).appendTo( $li );
    91                         // Stash raw numbers on the row for the live estimate.
    92                         $li.data( 'bytes', it.bytes || 0 ).data( 'w', it.w || 0 ).data( 'fmt', it.fmt || '' );
    93                         if ( it.alpha ) { $li.addClass( 'has-alpha' ); }
    94                         $s.append( $li );
    95                 } );
    96                 var $note = $( '.tiax-opt-samplenote' );
    97                 if ( ( d.count || 0 ) > samples.length ) { $note.text( '+ ' + ( d.count - samples.length ) + ' more' ).show(); } else { $note.hide(); }
    98                 // Transparency warning shows only when JPEG is the (global) target + some images have alpha.
    99                 refreshAlphaWarn( d.alpha || 0 );
    100                 refreshEstimates();
    101                 applyRowFilters();
    102                 $( '.tiax-opt-ack' ).prop( 'checked', false );
    103                 $( '.tiax-opt-all' ).prop( 'checked', false );
    104                 updateSelected();
     52                if ( it.f === '__keep' ) { format = ''; }       // explicitly keep source format
     53                else if ( it.f ) { format = it.f; }             // explicit target
     54                else { format = globalFormat(); }               // follow global
     55                var quality = it.q ? parseInt( it.q, 10 ) : globalQuality();
     56                return { id: it.id, format: format, quality: quality };
     57        }
     58
     59        function itemById( id ) {
     60                for ( var i = 0; i < ITEMS.length; i++ ) { if ( ITEMS[ i ].id === id ) { return ITEMS[ i ]; } }
     61                return null;
    10562        }
    10663
    … …  
    11774                }
    11875        }
    119         // Estimate one row's projected new size at its EFFECTIVE settings; write the saving cell.
    120         function estimateRow( $row ) {
    121                 var bytes = $row.data( 'bytes' ) || 0;
    122                 var w     = $row.data( 'w' ) || 0;
    123                 var srcFmt = ( $row.data( 'fmt' ) || '' ).replace( 'jpg', 'jpeg' );
    124                 if ( ! bytes ) { $row.find( '.tiax-opt-est' ).text( '' ); return; }
    125                 var s = rowSettings( $row );
     76        // Estimate one image's projected saving at its EFFECTIVE settings → { cls, text }.
     77        function estimate( it ) {
     78                var bytes = it.bytes || 0;
     79                if ( ! bytes ) { return { cls: '', text: '' }; }
     80                var srcFmt = ( it.fmt || '' ).replace( 'jpg', 'jpeg' );
     81                var s = itemSettings( it );
    12682                var target = s.format || srcFmt;
    12783                var maxw = globalMaxW();
    128                 var oversized = ( maxw > 0 && w > 0 && maxw < w );
     84                var oversized = ( maxw > 0 && it.w > 0 && maxw < it.w );
    12985                var saved;
    13086                // Already the target format AND not being resized → re-compression saves little; be honest
    … …  
    13490                } else {
    13591                        var ratio = formatRatio( target, s.quality );
    136                         if ( oversized ) { ratio *= Math.pow( maxw / w, 2 ); }
     92                        if ( oversized ) { ratio *= Math.pow( maxw / it.w, 2 ); }
    13793                        saved = bytes - Math.round( bytes * ratio );
    13894                }
    139                 var $c = $row.find( '.tiax-opt-est' ).removeClass( 'tiax-opt-est--save tiax-opt-est--grow tiax-opt-est--none' );
    14095                var pct = Math.round( saved / bytes * 100 );
    141                 if ( saved > bytes * 0.02 ) { $c.addClass( 'tiax-opt-est--save' ).text( '~−' + human( saved ) + ' (' + pct + '%)' ); }
    142                 else if ( saved < -bytes * 0.02 ) { $c.addClass( 'tiax-opt-est--grow' ).text( '~+' + human( -saved ) ); }
    143                 else { $c.addClass( 'tiax-opt-est--none' ).text( '~ no gain' ); }
    144         }
    145         function refreshEstimates() { $( '.tiax-opt-item' ).each( function () { estimateRow( $( this ) ); } ); }
    146 
    147         // List filters: hide rows the run shouldn't touch — files already in the (effective) target
    148         // format, and files with no estimated gain. Re-encoding those wastes time and can degrade the
    149         // original. Hidden rows are UNTICKED and excluded from Select all, so the filters protect the
    150         // run rather than just tidying the list. A same-format file that the max-width would resize
    151         // still shows (resizing genuinely saves).
    152         function applyRowFilters() {
     96                if ( saved > bytes * 0.02 ) { return { cls: 'tiax-opt-est--save', text: '~−' + human( saved ) + ' (' + pct + '%)' }; }
     97                if ( saved < -bytes * 0.02 ) { return { cls: 'tiax-opt-est--grow', text: '~+' + human( -saved ) }; }
     98                return { cls: 'tiax-opt-est--none', text: '~ no gain' };
     99        }
     100
     101        // Should the list filters hide this image? Re-encoding a file that's already in the target
     102        // format (or shows no gain) wastes time and can degrade the original. A same-format file that
     103        // the max-width would resize still shows (resizing genuinely saves).
     104        function isFiltered( it ) {
    153105                var hideSame = $( '.tiax-opt-hide-samefmt' ).is( ':checked' );
    154106                var hideNone = $( '.tiax-opt-hide-nogain' ).is( ':checked' );
     107                var srcFmt   = ( it.fmt || '' ).replace( 'jpg', 'jpeg' );
     108                var target   = itemSettings( it ).format;   // '' = keep original → same-format filter doesn't apply.
    155109                var maxw     = globalMaxW();
    156                 var hidden   = 0;
    157                 $( '.tiax-opt-item' ).each( function () {
    158                         var $row   = $( this );
    159                         var srcFmt = ( $row.data( 'fmt' ) || '' ).replace( 'jpg', 'jpeg' );
    160                         var w      = $row.data( 'w' ) || 0;
    161                         var target = rowSettings( $row ).format;   // '' = keep original → same-format filter doesn't apply.
    162                         var oversized = ( maxw > 0 && w > 0 && maxw < w );
    163                         var $est   = $row.find( '.tiax-opt-est' );
    164                         var same   = hideSame && '' !== target && srcFmt === target && ! oversized;
    165                         var none   = hideNone && ( $est.hasClass( 'tiax-opt-est--none' ) || $est.hasClass( 'tiax-opt-est--grow' ) );
    166                         var hide   = same || none;
    167                         $row.toggleClass( 'is-filtered', hide ).toggle( ! hide );
    168                         if ( hide ) { $row.find( '.tiax-opt-pick' ).prop( 'checked', false ); hidden++; }
    169                 } );
    170                 var $c = $( '.tiax-opt-filters__count' );
    171                 $c.text( hidden > 0 ? ( I18N.hiddenN || '%n hidden by filters' ).replace( '%n', hidden ) : '' );
     110                var oversized = ( maxw > 0 && it.w > 0 && maxw < it.w );
     111                if ( hideSame && '' !== target && srcFmt === target && ! oversized ) { return true; }
     112                if ( hideNone && ( 'tiax-opt-est--none' === it.est.cls || 'tiax-opt-est--grow' === it.est.cls ) ) { return true; }
     113                return false;
     114        }
     115
     116        /* ─── Drawing the list ─────────────────────────────────────────────────── */
     117
     118        function buildRow( it ) {
     119                var $li = $( '<li class="tiax-opt-item">' ).attr( 'data-id', it.id );
     120                $( '<input type="checkbox" class="tiax-opt-pick">' ).attr( 'data-id', it.id ).prop( 'checked', !! it.picked ).appendTo( $li );
     121                if ( it.thumb ) { $( '<img class="tiax-unused-thumb" alt="" loading="lazy">' ).attr( 'src', it.thumb ).appendTo( $li ); }
     122                else { $li.append( '<span class="tiax-unused-thumb tiax-unused-thumb--none" aria-hidden="true"></span>' ); }
     123                $( '<span class="tiax-opt-name">' ).text( it.name ).appendTo( $li );
     124                $( '<span class="tiax-opt-cur">' ).text( ( it.fmt ? it.fmt.toUpperCase() + ' · ' : '' ) + ( it.size || '' ) ).appendTo( $li );
     125                $( '<span class="tiax-opt-est">' ).addClass( it.est.cls ).text( it.est.text ).appendTo( $li );
     126                formatSelect( it.f ).appendTo( $li );
     127                qualitySelect( it.q ).appendTo( $li );
     128                if ( it.alpha ) { $li.addClass( 'has-alpha' ); }
     129                return $li;
     130        }
     131
     132        // Draw the next chunk of VISIBLE rows (built off-page, added in one go).
     133        function drawChunk() {
     134                if ( shown >= VISIBLE.length ) { return; }
     135                var frag = document.createDocumentFragment();
     136                var end = Math.min( VISIBLE.length, shown + CHUNK );
     137                for ( var i = shown; i < end; i++ ) { frag.appendChild( buildRow( VISIBLE[ i ] )[ 0 ] ); }
     138                shown = end;
     139                $( '.tiax-opt-sample' )[ 0 ].appendChild( frag );
     140                updateMoreNote();
     141        }
     142
     143        function updateMoreNote() {
     144                var $note = $( '.tiax-opt-samplenote' );
     145                if ( shown < VISIBLE.length ) {
     146                        $note.text( ( I18N.showingN || 'Showing %s of %t, scroll the list for more' ).replace( '%s', shown ).replace( '%t', VISIBLE.length ) ).show();
     147                } else { $note.hide(); }
     148        }
     149
     150        // Re-apply the filters to the whole list and redraw from the top. Picks are kept, except on
     151        // images the filters now hide (hidden images are never part of a run).
     152        function refilter() {
     153                VISIBLE = [];
     154                var hidden = 0;
     155                ITEMS.forEach( function ( it ) {
     156                        it.est = estimate( it );
     157                        if ( isFiltered( it ) ) { it.picked = false; hidden++; }
     158                        else { VISIBLE.push( it ); }
     159                } );
     160                $( '.tiax-opt-filters__count' ).text( hidden > 0 ? ( I18N.hiddenN || '%n hidden by filters' ).replace( '%n', hidden ) : '' );
    172161                // Filters emptied the whole list → say so plainly instead of showing a bare void
    173162                // (e.g. target PNG: photos only ever GROW as PNG, so every row hides — correct, but
    174163                // baffling without an explanation).
    175                 var listEmpty = $( '.tiax-opt-item' ).length > 0 && 0 === $( '.tiax-opt-item:visible' ).length;
    176                 $( '.tiax-opt-nofilter-note' ).prop( 'hidden', ! listEmpty );
     164                $( '.tiax-opt-nofilter-note' ).prop( 'hidden', ! ( ITEMS.length > 0 && 0 === VISIBLE.length ) );
     165                var $s = $( '.tiax-opt-sample' ).empty();
     166                $s.scrollTop( 0 );
     167                shown = 0;
     168                drawChunk();
    177169                updateSelected();
    178170        }
    179 
    180         function refreshAlphaWarn( alphaCount ) {
    181                 var jpegGlobal = 'jpeg' === globalFormat();
    182                 var show = jpegGlobal && alphaCount > 0;
     171        var refilterTimer = 0;
     172        function refilterSoon() { clearTimeout( refilterTimer ); refilterTimer = setTimeout( refilter, 120 ); }
     173
     174        function pickedItems() { return VISIBLE.filter( function ( it ) { return it.picked; } ); }
     175        function updateSelected() {
     176                var n = pickedItems().length;
     177                var total = VISIBLE.length;   // only images the filters show count toward "all".
     178                var acked = $( '.tiax-opt-ack' ).is( ':checked' );
     179                $( '.tiax-opt-go' ).prop( 'disabled', ! ( n > 0 && acked ) ).text( ( I18N.runSel || 'Optimise %n selected' ).replace( '%n', n ) );
     180                $( '.tiax-opt-all' ).prop( 'checked', total > 0 && n === total );
     181        }
     182
     183        function refreshAlphaWarn() {
     184                var show = 'jpeg' === globalFormat() && ITEMS.some( function ( it ) { return it.alpha; } );
    183185                $( '.tiax-opt-alphawarn' ).prop( 'hidden', ! show );
    184186                if ( show ) { $( '.tiax-opt-alphawarn-text' ).text( I18N.alphaWarn || '' ); }
    185187        }
    186188
     189        // Return the whole panel to its clean pre-scan state: hide the progress bar + results
     190        // block, empty the list, zero the counters, clear the run status and untick the ack.
     191        // (The completion confirmation lives in .tiax-opt-donemsg, OUTSIDE results, so it stays.)
     192        function resetPanel() {
     193                ITEMS = []; VISIBLE = []; shown = 0;
     194                var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', true );
     195                $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
     196                $prog.find( '.tiax-fix-progress__label' ).text( '' );
     197                $( '.tiax-opt-results' ).prop( 'hidden', true );
     198                $( '.tiax-opt-sample' ).empty();
     199                $( '.tiax-opt-samplenote' ).hide();
     200                $( '.tiax-opt-count' ).text( '0' );
     201                $( '.tiax-opt-total' ).text( '0 B' );
     202                $( '.tiax-opt-filters__count' ).text( '' );
     203                $( '.tiax-opt-nofilter-note, .tiax-opt-alphawarn' ).prop( 'hidden', true );
     204                $( '.tiax-opt-runstatus, .tiax-opt-status' ).text( '' );
     205                $( '.tiax-opt-ack' ).prop( 'checked', false );
     206                $( '.tiax-opt-all' ).prop( 'checked', false );
     207                $( '.tiax-opt-go' ).prop( 'disabled', true );
     208        }
     209
     210        function renderResults( d ) {
     211                $( '.tiax-opt-donemsg' ).prop( 'hidden', true ); // a fresh scan clears the last run's confirmation
     212                $( '.tiax-opt-results' ).prop( 'hidden', false );
     213                $( '.tiax-opt-count' ).text( d.count || 0 );
     214                $( '.tiax-opt-total' ).text( d.human || '0 B' );
     215                $( '.tiax-opt-ack' ).prop( 'checked', false );
     216                ITEMS.forEach( function ( it ) { it.picked = false; } );
     217                refreshAlphaWarn();
     218                refilter();
     219        }
     220
    187221        function esc( s ) { return String( s ).replace( /[&<>"]/g, function ( c ) { return { '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[ c ]; } ); }
    188222
    189223        $( function () {
    190                 // Live quality read-out + re-estimate on any global control change (filters re-apply —
    191                 // changed settings change which rows are pointless).
    192                 $( document ).on( 'input', '.tiax-opt-quality-global', function () { $( '.tiax-opt-qval' ).text( $( this ).val() ); refreshEstimates(); applyRowFilters(); } );
    193                 $( document ).on( 'change input', '.tiax-opt-maxw-global', function () { refreshEstimates(); applyRowFilters(); } );
     224                // Live quality read-out + re-filter on any global control change (changed settings change
     225                // which rows are pointless). Debounced: the slider fires many times while dragging.
     226                $( document ).on( 'input', '.tiax-opt-quality-global', function () { $( '.tiax-opt-qval' ).text( $( this ).val() ); refilterSoon(); } );
     227                $( document ).on( 'change input', '.tiax-opt-maxw-global', refilterSoon );
    194228                // Changing the global format re-evaluates the transparency warning + estimates + filters.
    195                 $( document ).on( 'change', '.tiax-opt-format-global', function () { refreshAlphaWarn( $( '.tiax-opt-item.has-alpha' ).length ); refreshEstimates(); applyRowFilters(); } );
    196                 // Per-row override changes re-estimate just that row (then re-filter — the override may
    197                 // have made it pointless, or worthwhile again).
    198                 $( document ).on( 'change', '.tiax-opt-row-format, .tiax-opt-row-quality', function () { estimateRow( $( this ).closest( 'li.tiax-opt-item' ) ); applyRowFilters(); } );
     229                $( document ).on( 'change', '.tiax-opt-format-global', function () { refreshAlphaWarn(); refilter(); } );
    199230                // The list filters themselves.
    200                 $( document ).on( 'change', '.tiax-opt-hide-samefmt, .tiax-opt-hide-nogain', applyRowFilters );
    201 
    202                 $( document ).on( 'change', '.tiax-opt-pick, .tiax-opt-ack', updateSelected );
    203                 $( document ).on( 'change', '.tiax-opt-all', function () { $( '.tiax-opt-pick:visible' ).prop( 'checked', $( this ).is( ':checked' ) ); updateSelected(); } );
     231                $( document ).on( 'change', '.tiax-opt-hide-samefmt, .tiax-opt-hide-nogain', refilter );
     232
     233                // Per-row override: store it on the image, re-estimate that row. If the override made the
     234                // row pointless, it drops out of the list (and the run) straight away.
     235                $( document ).on( 'change', '.tiax-opt-row-format, .tiax-opt-row-quality', function () {
     236                        var $li = $( this ).closest( 'li.tiax-opt-item' );
     237                        var it = itemById( parseInt( $li.attr( 'data-id' ), 10 ) );
     238                        if ( ! it ) { return; }
     239                        it.f = $li.find( '.tiax-opt-row-format' ).val() || '';
     240                        it.q = $li.find( '.tiax-opt-row-quality' ).val() || '';
     241                        it.est = estimate( it );
     242                        if ( isFiltered( it ) ) {
     243                                it.picked = false;
     244                                var at = VISIBLE.indexOf( it );
     245                                if ( at > -1 ) { VISIBLE.splice( at, 1 ); if ( at < shown ) { shown--; } }
     246                                $li.remove();
     247                                drawChunk();
     248                                updateMoreNote();
     249                        } else {
     250                                $li.find( '.tiax-opt-est' ).removeClass( 'tiax-opt-est--save tiax-opt-est--grow tiax-opt-est--none' ).addClass( it.est.cls ).text( it.est.text );
     251                        }
     252                        updateSelected();
     253                } );
     254
     255                $( document ).on( 'change', '.tiax-opt-pick', function () {
     256                        var it = itemById( parseInt( $( this ).attr( 'data-id' ), 10 ) );
     257                        if ( it ) { it.picked = $( this ).is( ':checked' ); }
     258                        updateSelected();
     259                } );
     260                $( document ).on( 'change', '.tiax-opt-ack', updateSelected );
     261                // Select all = every image the filters show, drawn or not yet drawn.
     262                $( document ).on( 'change', '.tiax-opt-all', function () {
     263                        var on = $( this ).is( ':checked' );
     264                        VISIBLE.forEach( function ( it ) { it.picked = on; } );
     265                        $( '.tiax-opt-pick' ).prop( 'checked', on );
     266                        updateSelected();
     267                } );
     268
     269                // Draw more rows as the list scrolls near its end.
     270                $( '.tiax-opt-sample' ).on( 'scroll', function () {
     271                        if ( this.scrollTop + this.clientHeight >= this.scrollHeight - 200 ) { drawChunk(); }
     272                } );
    204273
    205274                // Scan.
    206275                $( document ).on( 'click', '.tiax-opt-scan', function () {
    207276                        var $btn = $( this ).prop( 'disabled', true );
     277                        var fresh = $( '.tiax-opt-fresh' ).is( ':checked' ) ? 1 : 0;
     278                        resetPanel();
    208279                        var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', false );
    209280                        var $bar = $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
    210281                        var $lbl = $prog.find( '.tiax-fix-progress__label' ).text( I18N.loading || 'Preparing…' );
     282                        var $status = $( '.tiax-opt-status' );
    211283                        $.post( CFG.ajaxUrl, { action: 'tiax_opt_list', nonce: CFG.nonce } )
    212284                                .done( function ( res ) {
    213285                                        if ( ! res || ! res.success || ! res.data ) { $btn.prop( 'disabled', false ); return; }
    214286                                        var ids = ( res.data.ids || [] ).slice(); var total = res.data.total || 0;
    215                                         if ( ! total ) { $bar.css( 'width', '100%' ); $lbl.text( '0 / 0' ); renderResults( { count: 0, human: '0 B', samples: [] } ); $btn.prop( 'disabled', false ); return; }
    216                                         var scanned = 0, last = { count: 0, convert: 0, human: '0 B', samples: [], alpha: 0 };
     287                                        if ( ! total ) { $bar.css( 'width', '100%' ); $lbl.text( '0 / 0' ); renderResults( { count: 0, human: '0 B' } ); $btn.prop( 'disabled', false ); return; }
     288                                        var scanned = 0, read = 0, last = { count: 0, convert: 0, human: '0 B' };
    217289                                        // Target format captured at scan start, so the "to optimise" tally counts
    218290                                        // only images that would actually be CONVERTED (JPG/PNG when target is WebP),
    … …  
    220292                                        var scanTarget = globalFormat();
    221293                                        function next() {
    222                                                 if ( ! ids.length ) { $bar.css( 'width', '100%' ); $lbl.text( scanned + ' / ' + total ); renderResults( last ); $btn.prop( 'disabled', false ); return; }
    223                                                 var batch = ids.splice( 0, 40 );
    224                                                 $.post( CFG.ajaxUrl, { action: 'tiax_opt_scan_batch', nonce: CFG.nonce, ids: batch.join( ',' ), target: scanTarget } )
     294                                                if ( ! ids.length ) {
     295                                                        $bar.css( 'width', '100%' ); $lbl.text( scanned + ' / ' + total );
     296                                                        $status.text( ( I18N.readN || '%r read from disk, %k remembered from earlier scans.' ).replace( '%r', read ).replace( '%k', Math.max( 0, ( last.count || 0 ) - read ) ) );
     297                                                        renderResults( last ); $btn.prop( 'disabled', false ); return;
     298                                                }
     299                                                var batch = ids.splice( 0, BATCH );
     300                                                $.post( CFG.ajaxUrl, { action: 'tiax_opt_scan_batch', nonce: CFG.nonce, ids: batch.join( ',' ), target: scanTarget, fresh: fresh } )
    225301                                                        .done( function ( r ) {
    226                                                                 if ( r && r.success && r.data ) { last = r.data; }
     302                                                                if ( r && r.success && r.data ) {
     303                                                                        last = r.data;
     304                                                                        read += r.data.read || 0;
     305                                                                        ( r.data.samples || [] ).forEach( function ( s ) {
     306                                                                                s.f = ''; s.q = ''; s.picked = false; s.est = { cls: '', text: '' };
     307                                                                                ITEMS.push( s );
     308                                                                        } );
     309                                                                }
    227310                                                                scanned += batch.length;
    228311                                                                $bar.css( 'width', Math.round( scanned / total * 100 ) + '%' );
    229312                                                                var toDo = scanTarget ? ( last.convert || 0 ) : ( last.count || 0 );
    230                                                                         $lbl.text( scanned + ' / ' + total + ' · ' + toDo + ' ' + ( I18N.withData || '' ) );
     313                                                                $lbl.text( scanned + ' / ' + total + ' · ' + toDo + ' ' + ( I18N.withData || '' ) );
    231314                                                                next();
    232315                                                        } )
    … …  
    240323                // Run.
    241324                $( document ).on( 'click', '.tiax-opt-go', function () {
    242                         var rows = pickedRows();
    243                         if ( ! rows.length || ! $( '.tiax-opt-ack' ).is( ':checked' ) ) { return; }
    244                         if ( ! window.confirm( ( I18N.confirmN || 'Optimise %n selected image(s)? Originals are rewritten.' ).replace( '%n', rows.length ) ) ) { return; }
     325                        var picked = pickedItems();
     326                        if ( ! picked.length || ! $( '.tiax-opt-ack' ).is( ':checked' ) ) { return; }
     327                        if ( ! window.confirm( ( I18N.confirmN || 'Optimise %n selected image(s)? Originals are rewritten.' ).replace( '%n', picked.length ) ) ) { return; }
    245328                        var $btn = $( this ).prop( 'disabled', true );
    246329                        var $status = $( '.tiax-opt-runstatus' ).text( I18N.saving || 'Optimising…' );
    247                         var items = rows.map( function () { return rowSettings( $( this ) ); } ).get();
     330                        var items = picked.map( itemSettings );
    248331                        var maxw = globalMaxW(), strip = globalStrip().join( ',' );
    249332                        var total = items.length, done = 0, failed = 0, saved = 0, relinked = 0;
    … …  
    254337                                        if ( failed ) { msg += ' · ' + failed + ' ' + ( I18N.failedN || 'failed' ); }
    255338                                        resetPanel();                               // back to the clean start state…
    256                                         $( '.tiax-opt-donemsg' ).html( '&#10003; ' + msg ).prop( 'hidden', false ); // …keeping the confirmation.
     339                                        $( '.tiax-opt-donemsg' ).html( '&#10003; ' + esc( msg ) ).prop( 'hidden', false ); // …keeping the confirmation.
    257340                                        $btn.prop( 'disabled', false );
    258341                                        return;
  • tieraxo-asset-booster/tags/0.1.15/includes/families/optimise/optimise.php

    r3777573 r3789757  
    6161                        /* translators: %n: number of images hidden by the list filters. */
    6262                        'hiddenN'   => __( '%n hidden by filters', 'tieraxo-asset-booster' ),
     63                        /* translators: %r: images read from disk, %k: images remembered from earlier scans. */
     64                        'readN'     => __( '%r read from disk, %k remembered from earlier scans.', 'tieraxo-asset-booster' ),
     65                        /* translators: %s: rows shown so far, %t: rows in the list. */
     66                        'showingN'  => __( 'Showing %s of %t, scroll the list for more', 'tieraxo-asset-booster' ),
    6367                ),
    6468        ) );
  • tieraxo-asset-booster/tags/0.1.15/includes/families/optimise/page.php

    r3739758 r3789757  
    7474                        <div class="tiax-opt-scanrow">
    7575                                <button type="button" class="tiax-btn tiax-btn--primary tiax-opt-scan"><?php esc_html_e( 'Scan images', 'tieraxo-asset-booster' ); ?></button>
     76                                <label class="tiax-opt-fresh-lbl" title="<?php esc_attr_e( 'Tieraxo remembers what it read from each image, so later scans only read new or changed files. Tick this to read every file again, for example after replacing files outside WordPress.', 'tieraxo-asset-booster' ); ?>">
     77                                        <input type="checkbox" class="tiax-opt-fresh"> <span><?php esc_html_e( 'Re-read every file', 'tieraxo-asset-booster' ); ?></span>
     78                                </label>
    7679                                <span class="tiax-opt-status" aria-live="polite"></span>
    7780                        </div>
  • tieraxo-asset-booster/tags/0.1.15/languages/tieraxo-asset-booster.pot

    r3787150 r3789757  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Tieraxo Asset Booster 0.1.14\n"
     5"Project-Id-Version: Tieraxo Asset Booster 0.1.15\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/tieraxo-asset-booster\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-11-06T05:03:58+00:00\n"
     12"POT-Creation-Date: 2026-11-07T14:36:07+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    179179#: includes/families/file-analytics/page.php:103
    180180#: includes/families/fixers/page.php:162
    181 #: includes/families/optimise/page.php:138
     181#: includes/families/optimise/page.php:141
    182182msgid "File Analytics"
    183183msgstr ""
    … …  
    691691#: includes/families/file-analytics/page.php:444
    692692#: includes/families/fixers/page.php:160
    693 #: includes/families/optimise/page.php:135
     693#: includes/families/optimise/page.php:138
    694694msgid "Quick Links"
    695695msgstr ""
    … …  
    15571557#: includes/families/optimise/optimise.php:51
    15581558#: pro/modules/fixers-register.php:83
     1559#: pro/modules/optimise/media-convert/media-convert.php:79
    15591560msgid "failed"
    15601561msgstr ""
    … …  
    15991600
    16001601#: includes/families/fixers/page.php:145
    1601 #: includes/families/optimise/page.php:131
     1602#: includes/families/optimise/page.php:134
    16021603#: includes/families/upload-triage/page.php:137
    16031604msgid "About this tool"
    … …  
    16951696
    16961697#: includes/families/fixers/panels/records.php:42
    1697 #: includes/families/optimise/page.php:105
     1698#: includes/families/optimise/page.php:108
    16981699#: pro/modules/fixers/panels/metadata.php:79
    16991700#: pro/modules/fixers/panels/unused.php:57
    … …  
    21262127#: includes/families/libview/blades/folder-bar/folder-bar.php:161
    21272128#: includes/families/libview/blades/folder-bar/folder-bar.php:272
    2128 #: includes/families/optimise/page.php:89
     2129#: includes/families/optimise/page.php:92
    21292130msgid "images"
    21302131msgstr ""
    … …  
    28862887
    28872888#: includes/families/optimise/optimise.php:52
     2889#: pro/modules/optimise/media-convert/media-convert.php:78
    28882890msgid "links updated"
    28892891msgstr ""
    28902892
    28912893#: includes/families/optimise/optimise.php:53
     2894#: pro/modules/optimise/media-convert/media-convert.php:76
    28922895msgid "saved"
    28932896msgstr ""
    … …  
    29202923msgstr ""
    29212924
     2925#. translators: %r: images read from disk, %k: images remembered from earlier scans.
     2926#: includes/families/optimise/optimise.php:64
     2927msgid "%r read from disk, %k remembered from earlier scans."
     2928msgstr ""
     2929
     2930#. translators: %s: rows shown so far, %t: rows in the list.
     2931#: includes/families/optimise/optimise.php:66
     2932#, php-format
     2933msgid "Showing %s of %t, scroll the list for more"
     2934msgstr ""
     2935
    29222936#: includes/families/optimise/page.php:25
    29232937msgid "Convert &amp; compress"
    … …  
    29412955
    29422956#: includes/families/optimise/page.php:46
     2957#: pro/modules/optimise/media-convert/media-convert.php:57
    29432958msgid "Convert to"
    29442959msgstr ""
    29452960
    29462961#: includes/families/optimise/page.php:48
     2962#: pro/modules/optimise/media-convert/media-convert.php:58
    29472963msgid "Keep original format"
    29482964msgstr ""
    29492965
    29502966#: includes/families/optimise/page.php:56
     2967#: pro/modules/optimise/media-convert/media-convert.php:59
    29512968msgid "Quality"
    29522969msgstr ""
    … …  
    29572974
    29582975#: includes/families/optimise/page.php:62
     2976#: pro/modules/optimise/media-convert/media-convert.php:60
    29592977msgid "Max width (px)"
    29602978msgstr ""
    29612979
    29622980#: includes/families/optimise/page.php:63
     2981#: pro/modules/optimise/media-convert/media-convert.php:61
    29632982msgid "no limit"
    29642983msgstr ""
    29652984
    29662985#: includes/families/optimise/page.php:67
     2986#: pro/modules/optimise/media-convert/media-convert.php:62
    29672987msgid "Also strip"
    29682988msgstr ""
    29692989
    29702990#: includes/families/optimise/page.php:68
     2991#: pro/modules/optimise/media-convert/media-convert.php:63
    29712992msgid "EXIF"
    29722993msgstr ""
    29732994
    29742995#: includes/families/optimise/page.php:69
     2996#: pro/modules/optimise/media-convert/media-convert.php:64
    29752997msgid "GPS"
    29762998msgstr ""
    29772999
    29783000#: includes/families/optimise/page.php:70
     3001#: pro/modules/optimise/media-convert/media-convert.php:65
    29793002msgid "Thumbnail"
    29803003msgstr ""
    … …  
    29843007msgstr ""
    29853008
    2986 #: includes/families/optimise/page.php:90
     3009#: includes/families/optimise/page.php:76
     3010msgid "Tieraxo remembers what it read from each image, so later scans only read new or changed files. Tick this to read every file again, for example after replacing files outside WordPress."
     3011msgstr ""
     3012
     3013#: includes/families/optimise/page.php:77
     3014msgid "Re-read every file"
     3015msgstr ""
     3016
     3017#: includes/families/optimise/page.php:93
    29873018msgid "current total"
    29883019msgstr ""
    29893020
    2990 #: includes/families/optimise/page.php:97
     3021#: includes/families/optimise/page.php:100
    29913022msgid "Hide files already in the target format"
    29923023msgstr ""
    29933024
    2994 #: includes/families/optimise/page.php:98
     3025#: includes/families/optimise/page.php:101
    29953026msgid "Hide files with no estimated gain"
    29963027msgstr ""
    29973028
    2998 #: includes/families/optimise/page.php:108
     3029#: includes/families/optimise/page.php:111
    29993030msgid "No files show a worthwhile gain with these settings — try a different target format or quality, or untick the filters above to see every file."
    30003031msgstr ""
    30013032
    3002 #: includes/families/optimise/page.php:113
     3033#: includes/families/optimise/page.php:116
    30033034msgid "Each row follows the global settings above unless you override its format or quality. Originals are rewritten and thumbnails regenerated."
    30043035msgstr ""
    30053036
    3006 #: includes/families/optimise/page.php:118
     3037#: includes/families/optimise/page.php:121
    30073038#: pro/modules/fixers/panels/metadata.php:96
     3039#: pro/modules/optimise/media-convert/media-convert.php:66
    30083040msgid "I understand this rewrites my original files, and I have a recent backup of this site."
    30093041msgstr ""
    30103042
    3011 #: includes/families/optimise/page.php:121
     3043#: includes/families/optimise/page.php:124
    30123044msgid "Optimise 0 selected"
    30133045msgstr ""
    30143046
    3015 #: includes/families/optimise/page.php:132
     3047#: includes/families/optimise/page.php:135
    30163048msgid "Convert and compress in bulk. Set a global format and quality, then override any image on its own row. Optionally cap the width and strip hidden data. Originals are rewritten — keep a backup."
    30173049msgstr ""
    30183050
    3019 #: includes/families/optimise/page.php:137
     3051#: includes/families/optimise/page.php:140
    30203052#: pro/modules/fixers/panels/metadata.php:25
    30213053msgid "Metadata slimmer"
    … …  
    43344366msgstr ""
    43354367
    4336 #: pro/modules/optimise/ajax.php:158
     4368#: pro/modules/optimise/ajax.php:169
     4369#: pro/modules/optimise/media-convert/media-convert.php:97
    43374370msgid "Optimiser engine unavailable."
    43384371msgstr ""
    … …  
    43554388
    43564389#: pro/modules/optimise/engine.php:170
     4390#: pro/modules/optimise/media-convert/media-convert.php:112
    43574391msgid "This server can’t create that format."
    43584392msgstr ""
    … …  
    43624396msgstr ""
    43634397
     4398#: pro/modules/optimise/media-convert/media-convert.php:19
     4399msgid "Convert tool"
     4400msgstr ""
     4401
     4402#: pro/modules/optimise/media-convert/media-convert.php:20
     4403msgid "Adds a Convert button to the Media Library toolbar: tick images, choose a format and quality, and convert just those images."
     4404msgstr ""
     4405
     4406#: pro/modules/optimise/media-convert/media-convert.php:53
     4407#: pro/modules/optimise/media-convert/media-convert.php:54
     4408msgid "Convert selected images"
     4409msgstr ""
     4410
     4411#: pro/modules/optimise/media-convert/media-convert.php:55
     4412msgid "Click images in the library to pick them (click again to unpick), then press Convert. Press Esc to stop."
     4413msgstr ""
     4414
     4415#: pro/modules/optimise/media-convert/media-convert.php:56
     4416msgid "Tick the images to convert in the list, then press Convert."
     4417msgstr ""
     4418
     4419#. translators: %n: number of selected images.
     4420#: pro/modules/optimise/media-convert/media-convert.php:68
     4421msgid "Convert %n selected"
     4422msgstr ""
     4423
     4424#. translators: %n: number of selected images.
     4425#: pro/modules/optimise/media-convert/media-convert.php:70
     4426msgid "Convert %n selected image(s)? Your original files are rewritten (keep a backup)."
     4427msgstr ""
     4428
     4429#: pro/modules/optimise/media-convert/media-convert.php:71
     4430msgid "Only images can be converted."
     4431msgstr ""
     4432
     4433#: pro/modules/optimise/media-convert/media-convert.php:72
     4434#: pro/modules/optimise/media-convert/media-convert.php:75
     4435msgid "Converted"
     4436msgstr ""
     4437
     4438#: pro/modules/optimise/media-convert/media-convert.php:73
     4439msgid "Clear selection"
     4440msgstr ""
     4441
     4442#: pro/modules/optimise/media-convert/media-convert.php:74
     4443msgid "Converting…"
     4444msgstr ""
     4445
     4446#: pro/modules/optimise/media-convert/media-convert.php:77
     4447msgid "already in that format, skipped"
     4448msgstr ""
     4449
     4450#: pro/modules/optimise/media-convert/media-convert.php:80
     4451msgid "non-image(s) left alone"
     4452msgstr ""
     4453
     4454#: pro/modules/optimise/media-convert/media-convert.php:81
     4455msgid "Refresh the list"
     4456msgstr ""
     4457
     4458#: pro/modules/optimise/media-convert/media-convert.php:82
     4459msgid "Converting to JPEG removes transparency from PNG, WebP and GIF images."
     4460msgstr ""
     4461
    43644462#: pro/pro-helpers.php:29
    43654463msgid "Backup acknowledgement required."
  • tieraxo-asset-booster/tags/0.1.15/readme.txt

    r3787150 r3789757  
    77Tested up to: 7.1
    88Requires PHP: 8.0
    9 Stable tag: 0.1.14
     9Stable tag: 0.1.15
    1010License: GPL-2.0-or-later
    1111License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    139139== Upgrade Notice ==
    140140
     141= 0.1.15 =
     142Optimiser scans of large libraries no longer freeze the page, and rescans only read new or changed images. Pro adds a Convert button to the Media Library toolbar.
     143
    141144= 0.1.14 =
    142145Pro customers: you can now activate your licence key after installing Pro. No changes to the free version.
    … …  
    173176
    174177== Changelog ==
     178
     179= 0.1.15 =
     180* Added (Pro): convert images straight from the Media Library. A new Convert button in the toolbar opens a panel where you pick images, choose a format, quality and maximum width, and convert just those, with no library scan. Images already in that format, and anything that isn't an image, are left alone.
     181* Improved (Pro): the Optimiser remembers what it read from each image, so a rescan only reads images that are new or have changed. A new "Re-read every file" tick reads them all again.
     182* Fixed (Pro): scanning a large library no longer slows the page to a halt near the end. The list now loads as you scroll, and Select all still covers every image the filters show.
    175183
    176184= 0.1.14 =
    … …  
    215223* Improved: Prune orphans (Pro) also finds the old-format sizes left behind by earlier conversions, unless another image of that name still uses them.
    216224
    217 = 0.1.5 =
    218 * Fixed: image dimensions in the Swap panel and Upload Triage could show a long decimal (for example "1791.818 × 1123.636 px"), because the browser sometimes reports a fractional size for a loaded image. They are now rounded to whole pixels.
    219 * Fixed: activating a Pro licence failed, because the licence and download service moved from robsnow.eu (no longer online) to robart-illustration.eu. Tieraxo now uses the new address, and the readme, Upgrade page and sidebar links point there too.
    220 * Fixed: in the "Add media" window, Tieraxo's folder controls overlapped WordPress's own toolbar at the bottom of the window. They now sit in one tidy row beside it.
    221 
    222 = 0.1.4 =
    223 * Changed: the folder allowance and the analytics scan size are now the same for every install and are not tied to any tier or licence. Both are generous usability defaults and can be raised or removed with the `tiax_folder_cap` and `tiax_fa_scan_cap` filters. No folder is ever locked. Pro adds separate tools (Optimiser, cleanup, Upload Triage and more); it does not lift these limits.
    224 * Fixed: corrected the Terms and Privacy links in the readme.
    225 
    226 Older versions (0.1.3 and earlier) are listed in changelog.txt in the plugin folder.
     225Older versions (0.1.5 and earlier) are listed in changelog.txt in the plugin folder.
  • tieraxo-asset-booster/tags/0.1.15/tieraxo-asset-booster.php

    r3787150 r3789757  
    44 * Plugin URI:  https://robart-illustration.eu/plugins-wordpress-development/tieraxo-asset-booster/
    55 * Description: Tieraxo Asset Booster — a Rob Art WordPress plugin.
    6  * Version:     0.1.14
     6 * Version:     0.1.15
    77 * Author:      robword
    88 * Author URI:  https://robart-illustration.eu
    … …  
    2424
    2525// ── Constants ────────────────────────────────────────────────────────────────
    26 define( 'TIAX_VERSION',      '0.1.14' );
     26define( 'TIAX_VERSION',      '0.1.15' );
    2727// Asset version = plugin version. Site-wide cache-busting (forcing browsers to
    2828// refetch CSS/JS once after an edit) is handled centrally by GSS Site Bits' Cache
    … …  
    3131// do NOT reintroduce a per-load time() bust here (it would double-bust and waste
    3232// caching). Enqueues use TIAX_ASSET_VER, not _VERSION, to keep that seam.
    33 define( 'TIAX_ASSET_VER',    '0.1.14' );
     33define( 'TIAX_ASSET_VER',    '0.1.15' );
    3434define( 'TIAX_PATH',         plugin_dir_path( __FILE__ ) );
    3535define( 'TIAX_URL',          plugin_dir_url( __FILE__ ) );
  • tieraxo-asset-booster/tags/0.1.15/uninstall.php

    r3712333 r3789757  
    6565// Per-attachment meta this plugin added.
    6666delete_post_meta_by_key( '_tiax_reviewed' );
     67delete_post_meta_by_key( '_tiax_opt_scan' ); // Optimiser scan register.
    6768
    6869// Tables — the skeleton's example table.
  • tieraxo-asset-booster/trunk/changelog.txt

    r3787150 r3789757  
    1 Tieraxo Asset Booster: changelog for versions 0.1.3 and earlier.
     1Tieraxo Asset Booster: changelog for versions 0.1.5 and earlier.
    22The latest versions are in readme.txt.
     3
     4= 0.1.5 =
     5* Fixed: image dimensions in the Swap panel and Upload Triage could show a long decimal (for example "1791.818 × 1123.636 px"), because the browser sometimes reports a fractional size for a loaded image. They are now rounded to whole pixels.
     6* Fixed: activating a Pro licence failed, because the licence and download service moved from robsnow.eu (no longer online) to robart-illustration.eu. Tieraxo now uses the new address, and the readme, Upgrade page and sidebar links point there too.
     7* Fixed: in the "Add media" window, Tieraxo's folder controls overlapped WordPress's own toolbar at the bottom of the window. They now sit in one tidy row beside it.
     8
     9= 0.1.4 =
     10* Changed: the folder allowance and the analytics scan size are now the same for every install and are not tied to any tier or licence. Both are generous usability defaults and can be raised or removed with the `tiax_folder_cap` and `tiax_fa_scan_cap` filters. No folder is ever locked. Pro adds separate tools (Optimiser, cleanup, Upload Triage and more); it does not lift these limits.
     11* Fixed: corrected the Terms and Privacy links in the readme.
    312
    413= 0.1.3 =
  • tieraxo-asset-booster/trunk/includes/families/optimise/optimise.css

    r3712333 r3789757  
    3232.tiax-opt-results { margin-top: 16px; }
    3333.tiax-opt-listhead { margin: 6px 0 8px; }
     34.tiax-opt-fresh-lbl { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #50575e; }
    3435.tiax-opt-samplenote { margin: 6px 0 0; font-size: 12px; color: #50575e; font-style: italic; }
    3536.tiax-opt-alphawarn-text { color: #8a5a00; }
  • tieraxo-asset-booster/trunk/includes/families/optimise/optimise.js

    r3777573 r3789757  
    33   Global settings (format / quality / max-width / strip) with per-row overrides. Batched scan →
    44   review rows (thumbnail, name, current size, per-row format + quality dropdowns) → run the shared
    5    engine per selected image with its EFFECTIVE settings (global default unless the row overrides). */
     5   engine per selected image with its EFFECTIVE settings (global default unless the row overrides).
     6
     7   The scanned list lives in memory (ITEMS), not in the page. Filters, Select all and the run all
     8   work on that list; only the rows that pass the filters are drawn, a chunk at a time as the list
     9   scrolls. So a library of thousands of images never builds thousands of rows at once (which froze
     10   the browser at the end of big scans). */
    611( function ( $ ) {
    712        'use strict';
    … …  
    1015        var I18N = CFG.i18n || {};
    1116        var FORMATS = CFG.formats || [];
     17
     18        var BATCH = 100;   // images per scan request (the server caps it at 100 too).
     19        var CHUNK = 150;   // rows drawn per chunk.
     20
     21        var ITEMS   = [];  // every scanned image: { id, name, thumb, size, bytes, w, fmt, alpha, f, q, picked, est }
     22        var VISIBLE = [];  // the ITEMS that pass the filters, in list order.
     23        var shown   = 0;   // how many of VISIBLE are drawn so far.
    1224
    1325        function globalFormat()  { return $( '.tiax-opt-format-global' ).val() || ''; }
    … …  
    1729
    1830        // A per-row format <select>: "Default" (follow global) + each writable format + Keep.
    19         function formatSelect() {
    20                 var opts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
    21                 opts += '<option value="__keep">' + esc( I18N.keepFmt || 'Keep format' ) + '</option>';
    22                 FORMATS.forEach( function ( f ) { opts += '<option value="' + f + '">' + f.toUpperCase() + '</option>'; } );
    23                 return '<select class="tiax-opt-row-format">' + opts + '</select>';
     31        var formatOpts = null, qualityOpts = null;
     32        function formatSelect( val ) {
     33                if ( null === formatOpts ) {
     34                        formatOpts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
     35                        formatOpts += '<option value="__keep">' + esc( I18N.keepFmt || 'Keep format' ) + '</option>';
     36                        FORMATS.forEach( function ( f ) { formatOpts += '<option value="' + esc( f ) + '">' + esc( f.toUpperCase() ) + '</option>'; } );
     37                }
     38                return $( '<select class="tiax-opt-row-format">' + formatOpts + '</select>' ).val( val || '' );
    2439        }
    2540        // A per-row quality <select>: "Default" + a few standard steps.
    26         function qualitySelect() {
    27                 var vals = [ 100, 92, 85, 82, 75, 65, 55, 40 ];
    28                 var opts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
    29                 vals.forEach( function ( v ) { opts += '<option value="' + v + '">' + v + '</option>'; } );
    30                 return '<select class="tiax-opt-row-quality">' + opts + '</select>';
    31         }
    32 
    33         // Effective settings for one row: override wins, else global.
    34         function rowSettings( $row ) {
    35                 var fSel = $row.find( '.tiax-opt-row-format' ).val();
    36                 var qSel = $row.find( '.tiax-opt-row-quality' ).val();
     41        function qualitySelect( val ) {
     42                if ( null === qualityOpts ) {
     43                        qualityOpts = '<option value="">' + esc( I18N.useGlobal || 'Default' ) + '</option>';
     44                        [ 100, 92, 85, 82, 75, 65, 55, 40 ].forEach( function ( v ) { qualityOpts += '<option value="' + v + '">' + v + '</option>'; } );
     45                }
     46                return $( '<select class="tiax-opt-row-quality">' + qualityOpts + '</select>' ).val( val || '' );
     47        }
     48
     49        // Effective settings for one image: its override wins, else global.
     50        function itemSettings( it ) {
    3751                var format;
    38                 if ( fSel === '__keep' ) { format = ''; }          // explicitly keep source format
    39                 else if ( fSel ) { format = fSel; }                // explicit target
    40                 else { format = globalFormat(); }                  // follow global
    41                 var quality = qSel ? parseInt( qSel, 10 ) : globalQuality();
    42                 return { id: parseInt( $row.find( '.tiax-opt-pick' ).data( 'id' ), 10 ), format: format, quality: quality };
    43         }
    44 
    45         function pickedRows() { return $( '.tiax-opt-pick:checked' ).closest( 'li.tiax-opt-item' ); }
    46         function updateSelected() {
    47                 var n = pickedRows().length;
    48                 // Only VISIBLE rows count toward "all" — filtered-out rows are excluded from the run.
    49                 var total = $( '.tiax-opt-pick:visible' ).length;
    50                 var acked = $( '.tiax-opt-ack' ).is( ':checked' );
    51                 $( '.tiax-opt-go' ).prop( 'disabled', ! ( n > 0 && acked ) ).text( ( I18N.runSel || 'Optimise %n selected' ).replace( '%n', n ) );
    52                 $( '.tiax-opt-all' ).prop( 'checked', total > 0 && n === total );
    53         }
    54 
    55         // Return the whole panel to its clean pre-scan state: hide the progress bar + results
    56         // block, empty the list, zero the counters, clear the run status and untick the ack.
    57         // (The completion confirmation lives in .tiax-opt-donemsg, OUTSIDE results, so it stays.)
    58         function resetPanel() {
    59                 var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', true );
    60                 $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
    61                 $prog.find( '.tiax-fix-progress__label' ).text( '' );
    62                 $( '.tiax-opt-results' ).prop( 'hidden', true );
    63                 $( '.tiax-opt-sample' ).empty();
    64                 $( '.tiax-opt-count' ).text( '0' );
    65                 $( '.tiax-opt-total' ).text( '0 B' );
    66                 $( '.tiax-opt-filters__count' ).text( '' );
    67                 $( '.tiax-opt-nofilter-note, .tiax-opt-alphawarn' ).prop( 'hidden', true );
    68                 $( '.tiax-opt-runstatus' ).text( '' );
    69                 $( '.tiax-opt-ack' ).prop( 'checked', false );
    70                 $( '.tiax-opt-all' ).prop( 'checked', false );
    71                 $( '.tiax-opt-go' ).prop( 'disabled', true );
    72         }
    73 
    74         function renderResults( d ) {
    75                 $( '.tiax-opt-donemsg' ).prop( 'hidden', true ); // a fresh scan clears the last run's confirmation
    76                 var $r = $( '.tiax-opt-results' ).prop( 'hidden', false );
    77                 $( '.tiax-opt-count' ).text( d.count || 0 );
    78                 $( '.tiax-opt-total' ).text( d.human || '0 B' );
    79                 var samples = d.samples || [];
    80                 var $s = $( '.tiax-opt-sample' ).empty();
    81                 samples.forEach( function ( it ) {
    82                         var $li = $( '<li class="tiax-opt-item">' );
    83                         $( '<input type="checkbox" class="tiax-opt-pick">' ).attr( 'data-id', it.id ).appendTo( $li );
    84                         if ( it.thumb ) { $( '<img class="tiax-unused-thumb" alt="" loading="lazy">' ).attr( 'src', it.thumb ).appendTo( $li ); }
    85                         else { $li.append( '<span class="tiax-unused-thumb tiax-unused-thumb--none" aria-hidden="true"></span>' ); }
    86                         $( '<span class="tiax-opt-name">' ).text( it.name ).appendTo( $li );
    87                         $( '<span class="tiax-opt-cur">' ).text( ( it.fmt ? it.fmt.toUpperCase() + ' · ' : '' ) + ( it.size || '' ) ).appendTo( $li );
    88                         $( '<span class="tiax-opt-est">' ).appendTo( $li );
    89                         $( formatSelect() ).appendTo( $li );
    90                         $( qualitySelect() ).appendTo( $li );
    91                         // Stash raw numbers on the row for the live estimate.
    92                         $li.data( 'bytes', it.bytes || 0 ).data( 'w', it.w || 0 ).data( 'fmt', it.fmt || '' );
    93                         if ( it.alpha ) { $li.addClass( 'has-alpha' ); }
    94                         $s.append( $li );
    95                 } );
    96                 var $note = $( '.tiax-opt-samplenote' );
    97                 if ( ( d.count || 0 ) > samples.length ) { $note.text( '+ ' + ( d.count - samples.length ) + ' more' ).show(); } else { $note.hide(); }
    98                 // Transparency warning shows only when JPEG is the (global) target + some images have alpha.
    99                 refreshAlphaWarn( d.alpha || 0 );
    100                 refreshEstimates();
    101                 applyRowFilters();
    102                 $( '.tiax-opt-ack' ).prop( 'checked', false );
    103                 $( '.tiax-opt-all' ).prop( 'checked', false );
    104                 updateSelected();
     52                if ( it.f === '__keep' ) { format = ''; }       // explicitly keep source format
     53                else if ( it.f ) { format = it.f; }             // explicit target
     54                else { format = globalFormat(); }               // follow global
     55                var quality = it.q ? parseInt( it.q, 10 ) : globalQuality();
     56                return { id: it.id, format: format, quality: quality };
     57        }
     58
     59        function itemById( id ) {
     60                for ( var i = 0; i < ITEMS.length; i++ ) { if ( ITEMS[ i ].id === id ) { return ITEMS[ i ]; } }
     61                return null;
    10562        }
    10663
    … …  
    11774                }
    11875        }
    119         // Estimate one row's projected new size at its EFFECTIVE settings; write the saving cell.
    120         function estimateRow( $row ) {
    121                 var bytes = $row.data( 'bytes' ) || 0;
    122                 var w     = $row.data( 'w' ) || 0;
    123                 var srcFmt = ( $row.data( 'fmt' ) || '' ).replace( 'jpg', 'jpeg' );
    124                 if ( ! bytes ) { $row.find( '.tiax-opt-est' ).text( '' ); return; }
    125                 var s = rowSettings( $row );
     76        // Estimate one image's projected saving at its EFFECTIVE settings → { cls, text }.
     77        function estimate( it ) {
     78                var bytes = it.bytes || 0;
     79                if ( ! bytes ) { return { cls: '', text: '' }; }
     80                var srcFmt = ( it.fmt || '' ).replace( 'jpg', 'jpeg' );
     81                var s = itemSettings( it );
    12682                var target = s.format || srcFmt;
    12783                var maxw = globalMaxW();
    128                 var oversized = ( maxw > 0 && w > 0 && maxw < w );
     84                var oversized = ( maxw > 0 && it.w > 0 && maxw < it.w );
    12985                var saved;
    13086                // Already the target format AND not being resized → re-compression saves little; be honest
    … …  
    13490                } else {
    13591                        var ratio = formatRatio( target, s.quality );
    136                         if ( oversized ) { ratio *= Math.pow( maxw / w, 2 ); }
     92                        if ( oversized ) { ratio *= Math.pow( maxw / it.w, 2 ); }
    13793                        saved = bytes - Math.round( bytes * ratio );
    13894                }
    139                 var $c = $row.find( '.tiax-opt-est' ).removeClass( 'tiax-opt-est--save tiax-opt-est--grow tiax-opt-est--none' );
    14095                var pct = Math.round( saved / bytes * 100 );
    141                 if ( saved > bytes * 0.02 ) { $c.addClass( 'tiax-opt-est--save' ).text( '~−' + human( saved ) + ' (' + pct + '%)' ); }
    142                 else if ( saved < -bytes * 0.02 ) { $c.addClass( 'tiax-opt-est--grow' ).text( '~+' + human( -saved ) ); }
    143                 else { $c.addClass( 'tiax-opt-est--none' ).text( '~ no gain' ); }
    144         }
    145         function refreshEstimates() { $( '.tiax-opt-item' ).each( function () { estimateRow( $( this ) ); } ); }
    146 
    147         // List filters: hide rows the run shouldn't touch — files already in the (effective) target
    148         // format, and files with no estimated gain. Re-encoding those wastes time and can degrade the
    149         // original. Hidden rows are UNTICKED and excluded from Select all, so the filters protect the
    150         // run rather than just tidying the list. A same-format file that the max-width would resize
    151         // still shows (resizing genuinely saves).
    152         function applyRowFilters() {
     96                if ( saved > bytes * 0.02 ) { return { cls: 'tiax-opt-est--save', text: '~−' + human( saved ) + ' (' + pct + '%)' }; }
     97                if ( saved < -bytes * 0.02 ) { return { cls: 'tiax-opt-est--grow', text: '~+' + human( -saved ) }; }
     98                return { cls: 'tiax-opt-est--none', text: '~ no gain' };
     99        }
     100
     101        // Should the list filters hide this image? Re-encoding a file that's already in the target
     102        // format (or shows no gain) wastes time and can degrade the original. A same-format file that
     103        // the max-width would resize still shows (resizing genuinely saves).
     104        function isFiltered( it ) {
    153105                var hideSame = $( '.tiax-opt-hide-samefmt' ).is( ':checked' );
    154106                var hideNone = $( '.tiax-opt-hide-nogain' ).is( ':checked' );
     107                var srcFmt   = ( it.fmt || '' ).replace( 'jpg', 'jpeg' );
     108                var target   = itemSettings( it ).format;   // '' = keep original → same-format filter doesn't apply.
    155109                var maxw     = globalMaxW();
    156                 var hidden   = 0;
    157                 $( '.tiax-opt-item' ).each( function () {
    158                         var $row   = $( this );
    159                         var srcFmt = ( $row.data( 'fmt' ) || '' ).replace( 'jpg', 'jpeg' );
    160                         var w      = $row.data( 'w' ) || 0;
    161                         var target = rowSettings( $row ).format;   // '' = keep original → same-format filter doesn't apply.
    162                         var oversized = ( maxw > 0 && w > 0 && maxw < w );
    163                         var $est   = $row.find( '.tiax-opt-est' );
    164                         var same   = hideSame && '' !== target && srcFmt === target && ! oversized;
    165                         var none   = hideNone && ( $est.hasClass( 'tiax-opt-est--none' ) || $est.hasClass( 'tiax-opt-est--grow' ) );
    166                         var hide   = same || none;
    167                         $row.toggleClass( 'is-filtered', hide ).toggle( ! hide );
    168                         if ( hide ) { $row.find( '.tiax-opt-pick' ).prop( 'checked', false ); hidden++; }
    169                 } );
    170                 var $c = $( '.tiax-opt-filters__count' );
    171                 $c.text( hidden > 0 ? ( I18N.hiddenN || '%n hidden by filters' ).replace( '%n', hidden ) : '' );
     110                var oversized = ( maxw > 0 && it.w > 0 && maxw < it.w );
     111                if ( hideSame && '' !== target && srcFmt === target && ! oversized ) { return true; }
     112                if ( hideNone && ( 'tiax-opt-est--none' === it.est.cls || 'tiax-opt-est--grow' === it.est.cls ) ) { return true; }
     113                return false;
     114        }
     115
     116        /* ─── Drawing the list ─────────────────────────────────────────────────── */
     117
     118        function buildRow( it ) {
     119                var $li = $( '<li class="tiax-opt-item">' ).attr( 'data-id', it.id );
     120                $( '<input type="checkbox" class="tiax-opt-pick">' ).attr( 'data-id', it.id ).prop( 'checked', !! it.picked ).appendTo( $li );
     121                if ( it.thumb ) { $( '<img class="tiax-unused-thumb" alt="" loading="lazy">' ).attr( 'src', it.thumb ).appendTo( $li ); }
     122                else { $li.append( '<span class="tiax-unused-thumb tiax-unused-thumb--none" aria-hidden="true"></span>' ); }
     123                $( '<span class="tiax-opt-name">' ).text( it.name ).appendTo( $li );
     124                $( '<span class="tiax-opt-cur">' ).text( ( it.fmt ? it.fmt.toUpperCase() + ' · ' : '' ) + ( it.size || '' ) ).appendTo( $li );
     125                $( '<span class="tiax-opt-est">' ).addClass( it.est.cls ).text( it.est.text ).appendTo( $li );
     126                formatSelect( it.f ).appendTo( $li );
     127                qualitySelect( it.q ).appendTo( $li );
     128                if ( it.alpha ) { $li.addClass( 'has-alpha' ); }
     129                return $li;
     130        }
     131
     132        // Draw the next chunk of VISIBLE rows (built off-page, added in one go).
     133        function drawChunk() {
     134                if ( shown >= VISIBLE.length ) { return; }
     135                var frag = document.createDocumentFragment();
     136                var end = Math.min( VISIBLE.length, shown + CHUNK );
     137                for ( var i = shown; i < end; i++ ) { frag.appendChild( buildRow( VISIBLE[ i ] )[ 0 ] ); }
     138                shown = end;
     139                $( '.tiax-opt-sample' )[ 0 ].appendChild( frag );
     140                updateMoreNote();
     141        }
     142
     143        function updateMoreNote() {
     144                var $note = $( '.tiax-opt-samplenote' );
     145                if ( shown < VISIBLE.length ) {
     146                        $note.text( ( I18N.showingN || 'Showing %s of %t, scroll the list for more' ).replace( '%s', shown ).replace( '%t', VISIBLE.length ) ).show();
     147                } else { $note.hide(); }
     148        }
     149
     150        // Re-apply the filters to the whole list and redraw from the top. Picks are kept, except on
     151        // images the filters now hide (hidden images are never part of a run).
     152        function refilter() {
     153                VISIBLE = [];
     154                var hidden = 0;
     155                ITEMS.forEach( function ( it ) {
     156                        it.est = estimate( it );
     157                        if ( isFiltered( it ) ) { it.picked = false; hidden++; }
     158                        else { VISIBLE.push( it ); }
     159                } );
     160                $( '.tiax-opt-filters__count' ).text( hidden > 0 ? ( I18N.hiddenN || '%n hidden by filters' ).replace( '%n', hidden ) : '' );
    172161                // Filters emptied the whole list → say so plainly instead of showing a bare void
    173162                // (e.g. target PNG: photos only ever GROW as PNG, so every row hides — correct, but
    174163                // baffling without an explanation).
    175                 var listEmpty = $( '.tiax-opt-item' ).length > 0 && 0 === $( '.tiax-opt-item:visible' ).length;
    176                 $( '.tiax-opt-nofilter-note' ).prop( 'hidden', ! listEmpty );
     164                $( '.tiax-opt-nofilter-note' ).prop( 'hidden', ! ( ITEMS.length > 0 && 0 === VISIBLE.length ) );
     165                var $s = $( '.tiax-opt-sample' ).empty();
     166                $s.scrollTop( 0 );
     167                shown = 0;
     168                drawChunk();
    177169                updateSelected();
    178170        }
    179 
    180         function refreshAlphaWarn( alphaCount ) {
    181                 var jpegGlobal = 'jpeg' === globalFormat();
    182                 var show = jpegGlobal && alphaCount > 0;
     171        var refilterTimer = 0;
     172        function refilterSoon() { clearTimeout( refilterTimer ); refilterTimer = setTimeout( refilter, 120 ); }
     173
     174        function pickedItems() { return VISIBLE.filter( function ( it ) { return it.picked; } ); }
     175        function updateSelected() {
     176                var n = pickedItems().length;
     177                var total = VISIBLE.length;   // only images the filters show count toward "all".
     178                var acked = $( '.tiax-opt-ack' ).is( ':checked' );
     179                $( '.tiax-opt-go' ).prop( 'disabled', ! ( n > 0 && acked ) ).text( ( I18N.runSel || 'Optimise %n selected' ).replace( '%n', n ) );
     180                $( '.tiax-opt-all' ).prop( 'checked', total > 0 && n === total );
     181        }
     182
     183        function refreshAlphaWarn() {
     184                var show = 'jpeg' === globalFormat() && ITEMS.some( function ( it ) { return it.alpha; } );
    183185                $( '.tiax-opt-alphawarn' ).prop( 'hidden', ! show );
    184186                if ( show ) { $( '.tiax-opt-alphawarn-text' ).text( I18N.alphaWarn || '' ); }
    185187        }
    186188
     189        // Return the whole panel to its clean pre-scan state: hide the progress bar + results
     190        // block, empty the list, zero the counters, clear the run status and untick the ack.
     191        // (The completion confirmation lives in .tiax-opt-donemsg, OUTSIDE results, so it stays.)
     192        function resetPanel() {
     193                ITEMS = []; VISIBLE = []; shown = 0;
     194                var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', true );
     195                $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
     196                $prog.find( '.tiax-fix-progress__label' ).text( '' );
     197                $( '.tiax-opt-results' ).prop( 'hidden', true );
     198                $( '.tiax-opt-sample' ).empty();
     199                $( '.tiax-opt-samplenote' ).hide();
     200                $( '.tiax-opt-count' ).text( '0' );
     201                $( '.tiax-opt-total' ).text( '0 B' );
     202                $( '.tiax-opt-filters__count' ).text( '' );
     203                $( '.tiax-opt-nofilter-note, .tiax-opt-alphawarn' ).prop( 'hidden', true );
     204                $( '.tiax-opt-runstatus, .tiax-opt-status' ).text( '' );
     205                $( '.tiax-opt-ack' ).prop( 'checked', false );
     206                $( '.tiax-opt-all' ).prop( 'checked', false );
     207                $( '.tiax-opt-go' ).prop( 'disabled', true );
     208        }
     209
     210        function renderResults( d ) {
     211                $( '.tiax-opt-donemsg' ).prop( 'hidden', true ); // a fresh scan clears the last run's confirmation
     212                $( '.tiax-opt-results' ).prop( 'hidden', false );
     213                $( '.tiax-opt-count' ).text( d.count || 0 );
     214                $( '.tiax-opt-total' ).text( d.human || '0 B' );
     215                $( '.tiax-opt-ack' ).prop( 'checked', false );
     216                ITEMS.forEach( function ( it ) { it.picked = false; } );
     217                refreshAlphaWarn();
     218                refilter();
     219        }
     220
    187221        function esc( s ) { return String( s ).replace( /[&<>"]/g, function ( c ) { return { '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[ c ]; } ); }
    188222
    189223        $( function () {
    190                 // Live quality read-out + re-estimate on any global control change (filters re-apply —
    191                 // changed settings change which rows are pointless).
    192                 $( document ).on( 'input', '.tiax-opt-quality-global', function () { $( '.tiax-opt-qval' ).text( $( this ).val() ); refreshEstimates(); applyRowFilters(); } );
    193                 $( document ).on( 'change input', '.tiax-opt-maxw-global', function () { refreshEstimates(); applyRowFilters(); } );
     224                // Live quality read-out + re-filter on any global control change (changed settings change
     225                // which rows are pointless). Debounced: the slider fires many times while dragging.
     226                $( document ).on( 'input', '.tiax-opt-quality-global', function () { $( '.tiax-opt-qval' ).text( $( this ).val() ); refilterSoon(); } );
     227                $( document ).on( 'change input', '.tiax-opt-maxw-global', refilterSoon );
    194228                // Changing the global format re-evaluates the transparency warning + estimates + filters.
    195                 $( document ).on( 'change', '.tiax-opt-format-global', function () { refreshAlphaWarn( $( '.tiax-opt-item.has-alpha' ).length ); refreshEstimates(); applyRowFilters(); } );
    196                 // Per-row override changes re-estimate just that row (then re-filter — the override may
    197                 // have made it pointless, or worthwhile again).
    198                 $( document ).on( 'change', '.tiax-opt-row-format, .tiax-opt-row-quality', function () { estimateRow( $( this ).closest( 'li.tiax-opt-item' ) ); applyRowFilters(); } );
     229                $( document ).on( 'change', '.tiax-opt-format-global', function () { refreshAlphaWarn(); refilter(); } );
    199230                // The list filters themselves.
    200                 $( document ).on( 'change', '.tiax-opt-hide-samefmt, .tiax-opt-hide-nogain', applyRowFilters );
    201 
    202                 $( document ).on( 'change', '.tiax-opt-pick, .tiax-opt-ack', updateSelected );
    203                 $( document ).on( 'change', '.tiax-opt-all', function () { $( '.tiax-opt-pick:visible' ).prop( 'checked', $( this ).is( ':checked' ) ); updateSelected(); } );
     231                $( document ).on( 'change', '.tiax-opt-hide-samefmt, .tiax-opt-hide-nogain', refilter );
     232
     233                // Per-row override: store it on the image, re-estimate that row. If the override made the
     234                // row pointless, it drops out of the list (and the run) straight away.
     235                $( document ).on( 'change', '.tiax-opt-row-format, .tiax-opt-row-quality', function () {
     236                        var $li = $( this ).closest( 'li.tiax-opt-item' );
     237                        var it = itemById( parseInt( $li.attr( 'data-id' ), 10 ) );
     238                        if ( ! it ) { return; }
     239                        it.f = $li.find( '.tiax-opt-row-format' ).val() || '';
     240                        it.q = $li.find( '.tiax-opt-row-quality' ).val() || '';
     241                        it.est = estimate( it );
     242                        if ( isFiltered( it ) ) {
     243                                it.picked = false;
     244                                var at = VISIBLE.indexOf( it );
     245                                if ( at > -1 ) { VISIBLE.splice( at, 1 ); if ( at < shown ) { shown--; } }
     246                                $li.remove();
     247                                drawChunk();
     248                                updateMoreNote();
     249                        } else {
     250                                $li.find( '.tiax-opt-est' ).removeClass( 'tiax-opt-est--save tiax-opt-est--grow tiax-opt-est--none' ).addClass( it.est.cls ).text( it.est.text );
     251                        }
     252                        updateSelected();
     253                } );
     254
     255                $( document ).on( 'change', '.tiax-opt-pick', function () {
     256                        var it = itemById( parseInt( $( this ).attr( 'data-id' ), 10 ) );
     257                        if ( it ) { it.picked = $( this ).is( ':checked' ); }
     258                        updateSelected();
     259                } );
     260                $( document ).on( 'change', '.tiax-opt-ack', updateSelected );
     261                // Select all = every image the filters show, drawn or not yet drawn.
     262                $( document ).on( 'change', '.tiax-opt-all', function () {
     263                        var on = $( this ).is( ':checked' );
     264                        VISIBLE.forEach( function ( it ) { it.picked = on; } );
     265                        $( '.tiax-opt-pick' ).prop( 'checked', on );
     266                        updateSelected();
     267                } );
     268
     269                // Draw more rows as the list scrolls near its end.
     270                $( '.tiax-opt-sample' ).on( 'scroll', function () {
     271                        if ( this.scrollTop + this.clientHeight >= this.scrollHeight - 200 ) { drawChunk(); }
     272                } );
    204273
    205274                // Scan.
    206275                $( document ).on( 'click', '.tiax-opt-scan', function () {
    207276                        var $btn = $( this ).prop( 'disabled', true );
     277                        var fresh = $( '.tiax-opt-fresh' ).is( ':checked' ) ? 1 : 0;
     278                        resetPanel();
    208279                        var $prog = $( '.tiax-opt-progress' ).prop( 'hidden', false );
    209280                        var $bar = $prog.find( '.tiax-fix-progress__bar' ).css( 'width', '0%' );
    210281                        var $lbl = $prog.find( '.tiax-fix-progress__label' ).text( I18N.loading || 'Preparing…' );
     282                        var $status = $( '.tiax-opt-status' );
    211283                        $.post( CFG.ajaxUrl, { action: 'tiax_opt_list', nonce: CFG.nonce } )
    212284                                .done( function ( res ) {
    213285                                        if ( ! res || ! res.success || ! res.data ) { $btn.prop( 'disabled', false ); return; }
    214286                                        var ids = ( res.data.ids || [] ).slice(); var total = res.data.total || 0;
    215                                         if ( ! total ) { $bar.css( 'width', '100%' ); $lbl.text( '0 / 0' ); renderResults( { count: 0, human: '0 B', samples: [] } ); $btn.prop( 'disabled', false ); return; }
    216                                         var scanned = 0, last = { count: 0, convert: 0, human: '0 B', samples: [], alpha: 0 };
     287                                        if ( ! total ) { $bar.css( 'width', '100%' ); $lbl.text( '0 / 0' ); renderResults( { count: 0, human: '0 B' } ); $btn.prop( 'disabled', false ); return; }
     288                                        var scanned = 0, read = 0, last = { count: 0, convert: 0, human: '0 B' };
    217289                                        // Target format captured at scan start, so the "to optimise" tally counts
    218290                                        // only images that would actually be CONVERTED (JPG/PNG when target is WebP),
    … …  
    220292                                        var scanTarget = globalFormat();
    221293                                        function next() {
    222                                                 if ( ! ids.length ) { $bar.css( 'width', '100%' ); $lbl.text( scanned + ' / ' + total ); renderResults( last ); $btn.prop( 'disabled', false ); return; }
    223                                                 var batch = ids.splice( 0, 40 );
    224                                                 $.post( CFG.ajaxUrl, { action: 'tiax_opt_scan_batch', nonce: CFG.nonce, ids: batch.join( ',' ), target: scanTarget } )
     294                                                if ( ! ids.length ) {
     295                                                        $bar.css( 'width', '100%' ); $lbl.text( scanned + ' / ' + total );
     296                                                        $status.text( ( I18N.readN || '%r read from disk, %k remembered from earlier scans.' ).replace( '%r', read ).replace( '%k', Math.max( 0, ( last.count || 0 ) - read ) ) );
     297                                                        renderResults( last ); $btn.prop( 'disabled', false ); return;
     298                                                }
     299                                                var batch = ids.splice( 0, BATCH );
     300                                                $.post( CFG.ajaxUrl, { action: 'tiax_opt_scan_batch', nonce: CFG.nonce, ids: batch.join( ',' ), target: scanTarget, fresh: fresh } )
    225301                                                        .done( function ( r ) {
    226                                                                 if ( r && r.success && r.data ) { last = r.data; }
     302                                                                if ( r && r.success && r.data ) {
     303                                                                        last = r.data;
     304                                                                        read += r.data.read || 0;
     305                                                                        ( r.data.samples || [] ).forEach( function ( s ) {
     306                                                                                s.f = ''; s.q = ''; s.picked = false; s.est = { cls: '', text: '' };
     307                                                                                ITEMS.push( s );
     308                                                                        } );
     309                                                                }
    227310                                                                scanned += batch.length;
    228311                                                                $bar.css( 'width', Math.round( scanned / total * 100 ) + '%' );
    229312                                                                var toDo = scanTarget ? ( last.convert || 0 ) : ( last.count || 0 );
    230                                                                         $lbl.text( scanned + ' / ' + total + ' · ' + toDo + ' ' + ( I18N.withData || '' ) );
     313                                                                $lbl.text( scanned + ' / ' + total + ' · ' + toDo + ' ' + ( I18N.withData || '' ) );
    231314                                                                next();
    232315                                                        } )
    … …  
    240323                // Run.
    241324                $( document ).on( 'click', '.tiax-opt-go', function () {
    242                         var rows = pickedRows();
    243                         if ( ! rows.length || ! $( '.tiax-opt-ack' ).is( ':checked' ) ) { return; }
    244                         if ( ! window.confirm( ( I18N.confirmN || 'Optimise %n selected image(s)? Originals are rewritten.' ).replace( '%n', rows.length ) ) ) { return; }
     325                        var picked = pickedItems();
     326                        if ( ! picked.length || ! $( '.tiax-opt-ack' ).is( ':checked' ) ) { return; }
     327                        if ( ! window.confirm( ( I18N.confirmN || 'Optimise %n selected image(s)? Originals are rewritten.' ).replace( '%n', picked.length ) ) ) { return; }
    245328                        var $btn = $( this ).prop( 'disabled', true );
    246329                        var $status = $( '.tiax-opt-runstatus' ).text( I18N.saving || 'Optimising…' );
    247                         var items = rows.map( function () { return rowSettings( $( this ) ); } ).get();
     330                        var items = picked.map( itemSettings );
    248331                        var maxw = globalMaxW(), strip = globalStrip().join( ',' );
    249332                        var total = items.length, done = 0, failed = 0, saved = 0, relinked = 0;
    … …  
    254337                                        if ( failed ) { msg += ' · ' + failed + ' ' + ( I18N.failedN || 'failed' ); }
    255338                                        resetPanel();                               // back to the clean start state…
    256                                         $( '.tiax-opt-donemsg' ).html( '&#10003; ' + msg ).prop( 'hidden', false ); // …keeping the confirmation.
     339                                        $( '.tiax-opt-donemsg' ).html( '&#10003; ' + esc( msg ) ).prop( 'hidden', false ); // …keeping the confirmation.
    257340                                        $btn.prop( 'disabled', false );
    258341                                        return;
  • tieraxo-asset-booster/trunk/includes/families/optimise/optimise.php

    r3777573 r3789757  
    6161                        /* translators: %n: number of images hidden by the list filters. */
    6262                        'hiddenN'   => __( '%n hidden by filters', 'tieraxo-asset-booster' ),
     63                        /* translators: %r: images read from disk, %k: images remembered from earlier scans. */
     64                        'readN'     => __( '%r read from disk, %k remembered from earlier scans.', 'tieraxo-asset-booster' ),
     65                        /* translators: %s: rows shown so far, %t: rows in the list. */
     66                        'showingN'  => __( 'Showing %s of %t, scroll the list for more', 'tieraxo-asset-booster' ),
    6367                ),
    6468        ) );
  • tieraxo-asset-booster/trunk/includes/families/optimise/page.php

    r3739758 r3789757  
    7474                        <div class="tiax-opt-scanrow">
    7575                                <button type="button" class="tiax-btn tiax-btn--primary tiax-opt-scan"><?php esc_html_e( 'Scan images', 'tieraxo-asset-booster' ); ?></button>
     76                                <label class="tiax-opt-fresh-lbl" title="<?php esc_attr_e( 'Tieraxo remembers what it read from each image, so later scans only read new or changed files. Tick this to read every file again, for example after replacing files outside WordPress.', 'tieraxo-asset-booster' ); ?>">
     77                                        <input type="checkbox" class="tiax-opt-fresh"> <span><?php esc_html_e( 'Re-read every file', 'tieraxo-asset-booster' ); ?></span>
     78                                </label>
    7679                                <span class="tiax-opt-status" aria-live="polite"></span>
    7780                        </div>
  • tieraxo-asset-booster/trunk/languages/tieraxo-asset-booster.pot

    r3787150 r3789757  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Tieraxo Asset Booster 0.1.14\n"
     5"Project-Id-Version: Tieraxo Asset Booster 0.1.15\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/tieraxo-asset-booster\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-11-06T05:03:58+00:00\n"
     12"POT-Creation-Date: 2026-11-07T14:36:07+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    179179#: includes/families/file-analytics/page.php:103
    180180#: includes/families/fixers/page.php:162
    181 #: includes/families/optimise/page.php:138
     181#: includes/families/optimise/page.php:141
    182182msgid "File Analytics"
    183183msgstr ""
    … …  
    691691#: includes/families/file-analytics/page.php:444
    692692#: includes/families/fixers/page.php:160
    693 #: includes/families/optimise/page.php:135
     693#: includes/families/optimise/page.php:138
    694694msgid "Quick Links"
    695695msgstr ""
    … …  
    15571557#: includes/families/optimise/optimise.php:51
    15581558#: pro/modules/fixers-register.php:83
     1559#: pro/modules/optimise/media-convert/media-convert.php:79
    15591560msgid "failed"
    15601561msgstr ""
    … …  
    15991600
    16001601#: includes/families/fixers/page.php:145
    1601 #: includes/families/optimise/page.php:131
     1602#: includes/families/optimise/page.php:134
    16021603#: includes/families/upload-triage/page.php:137
    16031604msgid "About this tool"
    … …  
    16951696
    16961697#: includes/families/fixers/panels/records.php:42
    1697 #: includes/families/optimise/page.php:105
     1698#: includes/families/optimise/page.php:108
    16981699#: pro/modules/fixers/panels/metadata.php:79
    16991700#: pro/modules/fixers/panels/unused.php:57
    … …  
    21262127#: includes/families/libview/blades/folder-bar/folder-bar.php:161
    21272128#: includes/families/libview/blades/folder-bar/folder-bar.php:272
    2128 #: includes/families/optimise/page.php:89
     2129#: includes/families/optimise/page.php:92
    21292130msgid "images"
    21302131msgstr ""
    … …  
    28862887
    28872888#: includes/families/optimise/optimise.php:52
     2889#: pro/modules/optimise/media-convert/media-convert.php:78
    28882890msgid "links updated"
    28892891msgstr ""
    28902892
    28912893#: includes/families/optimise/optimise.php:53
     2894#: pro/modules/optimise/media-convert/media-convert.php:76
    28922895msgid "saved"
    28932896msgstr ""
    … …  
    29202923msgstr ""
    29212924
     2925#. translators: %r: images read from disk, %k: images remembered from earlier scans.
     2926#: includes/families/optimise/optimise.php:64
     2927msgid "%r read from disk, %k remembered from earlier scans."
     2928msgstr ""
     2929
     2930#. translators: %s: rows shown so far, %t: rows in the list.
     2931#: includes/families/optimise/optimise.php:66
     2932#, php-format
     2933msgid "Showing %s of %t, scroll the list for more"
     2934msgstr ""
     2935
    29222936#: includes/families/optimise/page.php:25
    29232937msgid "Convert &amp; compress"
    … …  
    29412955
    29422956#: includes/families/optimise/page.php:46
     2957#: pro/modules/optimise/media-convert/media-convert.php:57
    29432958msgid "Convert to"
    29442959msgstr ""
    29452960
    29462961#: includes/families/optimise/page.php:48
     2962#: pro/modules/optimise/media-convert/media-convert.php:58
    29472963msgid "Keep original format"
    29482964msgstr ""
    29492965
    29502966#: includes/families/optimise/page.php:56
     2967#: pro/modules/optimise/media-convert/media-convert.php:59
    29512968msgid "Quality"
    29522969msgstr ""
    … …  
    29572974
    29582975#: includes/families/optimise/page.php:62
     2976#: pro/modules/optimise/media-convert/media-convert.php:60
    29592977msgid "Max width (px)"
    29602978msgstr ""
    29612979
    29622980#: includes/families/optimise/page.php:63
     2981#: pro/modules/optimise/media-convert/media-convert.php:61
    29632982msgid "no limit"
    29642983msgstr ""
    29652984
    29662985#: includes/families/optimise/page.php:67
     2986#: pro/modules/optimise/media-convert/media-convert.php:62
    29672987msgid "Also strip"
    29682988msgstr ""
    29692989
    29702990#: includes/families/optimise/page.php:68
     2991#: pro/modules/optimise/media-convert/media-convert.php:63
    29712992msgid "EXIF"
    29722993msgstr ""
    29732994
    29742995#: includes/families/optimise/page.php:69
     2996#: pro/modules/optimise/media-convert/media-convert.php:64
    29752997msgid "GPS"
    29762998msgstr ""
    29772999
    29783000#: includes/families/optimise/page.php:70
     3001#: pro/modules/optimise/media-convert/media-convert.php:65
    29793002msgid "Thumbnail"
    29803003msgstr ""
    … …  
    29843007msgstr ""
    29853008
    2986 #: includes/families/optimise/page.php:90
     3009#: includes/families/optimise/page.php:76
     3010msgid "Tieraxo remembers what it read from each image, so later scans only read new or changed files. Tick this to read every file again, for example after replacing files outside WordPress."
     3011msgstr ""
     3012
     3013#: includes/families/optimise/page.php:77
     3014msgid "Re-read every file"
     3015msgstr ""
     3016
     3017#: includes/families/optimise/page.php:93
    29873018msgid "current total"
    29883019msgstr ""
    29893020
    2990 #: includes/families/optimise/page.php:97
     3021#: includes/families/optimise/page.php:100
    29913022msgid "Hide files already in the target format"
    29923023msgstr ""
    29933024
    2994 #: includes/families/optimise/page.php:98
     3025#: includes/families/optimise/page.php:101
    29953026msgid "Hide files with no estimated gain"
    29963027msgstr ""
    29973028
    2998 #: includes/families/optimise/page.php:108
     3029#: includes/families/optimise/page.php:111
    29993030msgid "No files show a worthwhile gain with these settings — try a different target format or quality, or untick the filters above to see every file."
    30003031msgstr ""
    30013032
    3002 #: includes/families/optimise/page.php:113
     3033#: includes/families/optimise/page.php:116
    30033034msgid "Each row follows the global settings above unless you override its format or quality. Originals are rewritten and thumbnails regenerated."
    30043035msgstr ""
    30053036
    3006 #: includes/families/optimise/page.php:118
     3037#: includes/families/optimise/page.php:121
    30073038#: pro/modules/fixers/panels/metadata.php:96
     3039#: pro/modules/optimise/media-convert/media-convert.php:66
    30083040msgid "I understand this rewrites my original files, and I have a recent backup of this site."
    30093041msgstr ""
    30103042
    3011 #: includes/families/optimise/page.php:121
     3043#: includes/families/optimise/page.php:124
    30123044msgid "Optimise 0 selected"
    30133045msgstr ""
    30143046
    3015 #: includes/families/optimise/page.php:132
     3047#: includes/families/optimise/page.php:135
    30163048msgid "Convert and compress in bulk. Set a global format and quality, then override any image on its own row. Optionally cap the width and strip hidden data. Originals are rewritten — keep a backup."
    30173049msgstr ""
    30183050
    3019 #: includes/families/optimise/page.php:137
     3051#: includes/families/optimise/page.php:140
    30203052#: pro/modules/fixers/panels/metadata.php:25
    30213053msgid "Metadata slimmer"
    … …  
    43344366msgstr ""
    43354367
    4336 #: pro/modules/optimise/ajax.php:158
     4368#: pro/modules/optimise/ajax.php:169
     4369#: pro/modules/optimise/media-convert/media-convert.php:97
    43374370msgid "Optimiser engine unavailable."
    43384371msgstr ""
    … …  
    43554388
    43564389#: pro/modules/optimise/engine.php:170
     4390#: pro/modules/optimise/media-convert/media-convert.php:112
    43574391msgid "This server can’t create that format."
    43584392msgstr ""
    … …  
    43624396msgstr ""
    43634397
     4398#: pro/modules/optimise/media-convert/media-convert.php:19
     4399msgid "Convert tool"
     4400msgstr ""
     4401
     4402#: pro/modules/optimise/media-convert/media-convert.php:20
     4403msgid "Adds a Convert button to the Media Library toolbar: tick images, choose a format and quality, and convert just those images."
     4404msgstr ""
     4405
     4406#: pro/modules/optimise/media-convert/media-convert.php:53
     4407#: pro/modules/optimise/media-convert/media-convert.php:54
     4408msgid "Convert selected images"
     4409msgstr ""
     4410
     4411#: pro/modules/optimise/media-convert/media-convert.php:55
     4412msgid "Click images in the library to pick them (click again to unpick), then press Convert. Press Esc to stop."
     4413msgstr ""
     4414
     4415#: pro/modules/optimise/media-convert/media-convert.php:56
     4416msgid "Tick the images to convert in the list, then press Convert."
     4417msgstr ""
     4418
     4419#. translators: %n: number of selected images.
     4420#: pro/modules/optimise/media-convert/media-convert.php:68
     4421msgid "Convert %n selected"
     4422msgstr ""
     4423
     4424#. translators: %n: number of selected images.
     4425#: pro/modules/optimise/media-convert/media-convert.php:70
     4426msgid "Convert %n selected image(s)? Your original files are rewritten (keep a backup)."
     4427msgstr ""
     4428
     4429#: pro/modules/optimise/media-convert/media-convert.php:71
     4430msgid "Only images can be converted."
     4431msgstr ""
     4432
     4433#: pro/modules/optimise/media-convert/media-convert.php:72
     4434#: pro/modules/optimise/media-convert/media-convert.php:75
     4435msgid "Converted"
     4436msgstr ""
     4437
     4438#: pro/modules/optimise/media-convert/media-convert.php:73
     4439msgid "Clear selection"
     4440msgstr ""
     4441
     4442#: pro/modules/optimise/media-convert/media-convert.php:74
     4443msgid "Converting…"
     4444msgstr ""
     4445
     4446#: pro/modules/optimise/media-convert/media-convert.php:77
     4447msgid "already in that format, skipped"
     4448msgstr ""
     4449
     4450#: pro/modules/optimise/media-convert/media-convert.php:80
     4451msgid "non-image(s) left alone"
     4452msgstr ""
     4453
     4454#: pro/modules/optimise/media-convert/media-convert.php:81
     4455msgid "Refresh the list"
     4456msgstr ""
     4457
     4458#: pro/modules/optimise/media-convert/media-convert.php:82
     4459msgid "Converting to JPEG removes transparency from PNG, WebP and GIF images."
     4460msgstr ""
     4461
    43644462#: pro/pro-helpers.php:29
    43654463msgid "Backup acknowledgement required."
  • tieraxo-asset-booster/trunk/readme.txt

    r3787150 r3789757  
    77Tested up to: 7.1
    88Requires PHP: 8.0
    9 Stable tag: 0.1.14
     9Stable tag: 0.1.15
    1010License: GPL-2.0-or-later
    1111License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    139139== Upgrade Notice ==
    140140
     141= 0.1.15 =
     142Optimiser scans of large libraries no longer freeze the page, and rescans only read new or changed images. Pro adds a Convert button to the Media Library toolbar.
     143
    141144= 0.1.14 =
    142145Pro customers: you can now activate your licence key after installing Pro. No changes to the free version.
    … …  
    173176
    174177== Changelog ==
     178
     179= 0.1.15 =
     180* Added (Pro): convert images straight from the Media Library. A new Convert button in the toolbar opens a panel where you pick images, choose a format, quality and maximum width, and convert just those, with no library scan. Images already in that format, and anything that isn't an image, are left alone.
     181* Improved (Pro): the Optimiser remembers what it read from each image, so a rescan only reads images that are new or have changed. A new "Re-read every file" tick reads them all again.
     182* Fixed (Pro): scanning a large library no longer slows the page to a halt near the end. The list now loads as you scroll, and Select all still covers every image the filters show.
    175183
    176184= 0.1.14 =
    … …  
    215223* Improved: Prune orphans (Pro) also finds the old-format sizes left behind by earlier conversions, unless another image of that name still uses them.
    216224
    217 = 0.1.5 =
    218 * Fixed: image dimensions in the Swap panel and Upload Triage could show a long decimal (for example "1791.818 × 1123.636 px"), because the browser sometimes reports a fractional size for a loaded image. They are now rounded to whole pixels.
    219 * Fixed: activating a Pro licence failed, because the licence and download service moved from robsnow.eu (no longer online) to robart-illustration.eu. Tieraxo now uses the new address, and the readme, Upgrade page and sidebar links point there too.
    220 * Fixed: in the "Add media" window, Tieraxo's folder controls overlapped WordPress's own toolbar at the bottom of the window. They now sit in one tidy row beside it.
    221 
    222 = 0.1.4 =
    223 * Changed: the folder allowance and the analytics scan size are now the same for every install and are not tied to any tier or licence. Both are generous usability defaults and can be raised or removed with the `tiax_folder_cap` and `tiax_fa_scan_cap` filters. No folder is ever locked. Pro adds separate tools (Optimiser, cleanup, Upload Triage and more); it does not lift these limits.
    224 * Fixed: corrected the Terms and Privacy links in the readme.
    225 
    226 Older versions (0.1.3 and earlier) are listed in changelog.txt in the plugin folder.
     225Older versions (0.1.5 and earlier) are listed in changelog.txt in the plugin folder.
  • tieraxo-asset-booster/trunk/tieraxo-asset-booster.php

    r3787150 r3789757  
    44 * Plugin URI:  https://robart-illustration.eu/plugins-wordpress-development/tieraxo-asset-booster/
    55 * Description: Tieraxo Asset Booster — a Rob Art WordPress plugin.
    6  * Version:     0.1.14
     6 * Version:     0.1.15
    77 * Author:      robword
    88 * Author URI:  https://robart-illustration.eu
    … …  
    2424
    2525// ── Constants ────────────────────────────────────────────────────────────────
    26 define( 'TIAX_VERSION',      '0.1.14' );
     26define( 'TIAX_VERSION',      '0.1.15' );
    2727// Asset version = plugin version. Site-wide cache-busting (forcing browsers to
    2828// refetch CSS/JS once after an edit) is handled centrally by GSS Site Bits' Cache
    … …  
    3131// do NOT reintroduce a per-load time() bust here (it would double-bust and waste
    3232// caching). Enqueues use TIAX_ASSET_VER, not _VERSION, to keep that seam.
    33 define( 'TIAX_ASSET_VER',    '0.1.14' );
     33define( 'TIAX_ASSET_VER',    '0.1.15' );
    3434define( 'TIAX_PATH',         plugin_dir_path( __FILE__ ) );
    3535define( 'TIAX_URL',          plugin_dir_url( __FILE__ ) );
  • tieraxo-asset-booster/trunk/uninstall.php

    r3712333 r3789757  
    6565// Per-attachment meta this plugin added.
    6666delete_post_meta_by_key( '_tiax_reviewed' );
     67delete_post_meta_by_key( '_tiax_opt_scan' ); // Optimiser scan register.
    6768
    6869// Tables — the skeleton's example table.
Note: See TracChangeset for help on using the changeset viewer.