Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/admin/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@
}
}

.cimo-media-upload-notice {
float: left;
clear: both;
box-sizing: border-box;
width: 100%;
margin: 1.5em 0 0;
padding: 10px 12px;
border-left: 4px solid #dba617;
background: #fff8e5;
color: #3c434a;
font-size: 13px;
line-height: 1.45;
}

.cimo-compression-savings {
font-size: 21px;
font-weight: 700;
Expand Down Expand Up @@ -202,4 +216,4 @@

.cimo-optimization-toggle-container:hover {
background: rgba(0, 0, 0, 0.7);
}
}
2 changes: 2 additions & 0 deletions src/admin/js/media-manager/drop-zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { domReady } from '~cimo/shared/dom-ready'
import { getFileConverter, requiresFileConversion } from '~cimo/shared/converters'
import { watchForEditorIframe } from '~cimo/shared/util'
import { saveMetadata } from '~cimo/shared/metadata-saver'
import { cacheConverterNotice } from '~cimo/shared/upload-notice-cache'
import { ProgressModal } from './progress-modal'
import { applyFilters } from '@wordpress/hooks'

Expand Down Expand Up @@ -114,6 +115,7 @@ function addDropZoneListenerToMediaManager( targetDocument ) {
fileConverters.map( async converter => {
try {
const result = await converter.optimize()
cacheConverterNotice( result )
if ( result.error ) {
// eslint-disable-next-line no-console
console.warn( result.error )
Expand Down
2 changes: 2 additions & 0 deletions src/admin/js/media-manager/select-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { domReady } from '~cimo/shared/dom-ready'
import { getFileConverter, requiresFileConversion } from '~cimo/shared/converters'
import { watchForEditorIframe } from '~cimo/shared/util'
import { saveMetadata } from '~cimo/shared/metadata-saver'
import { cacheConverterNotice } from '~cimo/shared/upload-notice-cache'
import { ProgressModal } from './progress-modal'
import { applyFilters } from '@wordpress/hooks'

Expand Down Expand Up @@ -92,6 +93,7 @@ function addSelectFilesListenerToFileUploads( targetDocument ) {
fileConverters.map( async converter => {
try {
const result = await converter.optimize()
cacheConverterNotice( result )
if ( result.error ) {
// eslint-disable-next-line no-console
console.warn( result.error )
Expand Down
44 changes: 44 additions & 0 deletions src/admin/js/media-manager/sidebar-info.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { domReady } from '~cimo/shared/dom-ready'
import { getCachedMetadata } from '~cimo/shared/metadata-saver'
import { buildPricingUrl } from '~cimo/shared/pricing-url'
import { getCachedUploadNotice } from '~cimo/shared/upload-notice-cache'
import { escape } from '~cimo/shared/util'
import { __, sprintf } from '@wordpress/i18n'
import { applyFilters } from '@wordpress/hooks'
Expand Down Expand Up @@ -64,6 +65,39 @@ function getMediaTypeLabel( mimetype ) {
}
}

/**
* Inject a temporary upload notice into the Media Library sidebar.
*
* @param {Object} options - Injection options.
* @param {Object} options.model - WordPress media attachment model.
* @param {Element} options.container - Sidebar container where the notice should appear.
*/
function injectCimoUploadNotice( {
model,
container,
} ) {
if ( ! model || ! container ) {
return
}

if ( container.querySelector( '.cimo-media-upload-notice' ) ) {
return
}

// Ask the temporary cache for a notice that belongs to this attachment filename.
const notice = getCachedUploadNotice( model.get( 'filename' ) )

if ( ! notice?.message ) {
return
}

// Append plain text only; the notice message comes from a converter result error.
const noticeElement = document.createElement( 'div' )
noticeElement.className = 'cimo-media-upload-notice'
noticeElement.textContent = notice.message
container.appendChild( noticeElement )
}

function injectCimoMetadata( {
model,
container,
Expand Down Expand Up @@ -285,6 +319,11 @@ domReady( () => {

const container = dom.querySelector( '.attachment-info' )

injectCimoUploadNotice( {
model: view.model,
container,
} )

injectCimoMetadata( {
model: view.model,
container,
Expand All @@ -307,6 +346,11 @@ domReady( () => {
'.attachment-info > .details'
)

injectCimoUploadNotice( {
model: this.model,
container,
} )

injectCimoMetadata( {
model: this.model,
container,
Expand Down
1 change: 1 addition & 0 deletions src/shared/converters/image-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ class ImageConverter extends Converter {
metadata: null,
reason: 'resulting-media-bigger-than-input',
error: `Resulting image is bigger than the input (input: ${ file.size } bytes, output: ${ convertedBlob.size } bytes), skipping conversion.`,
notice: __( 'Media is not resized because the uploaded media is already optimized.', 'cimo-image-optimizer' ),
}
}

Expand Down
130 changes: 130 additions & 0 deletions src/shared/upload-notice-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@

/**
* This script is in charge of keeping track of temporary upload notices for
* media conversion results, then matching those notices back to the Media
* Library attachment after WordPress finishes uploading and renaming the file.
* The cache is kept in memory only, so refreshing the page clears all notices.
*/

/**
* Create the cache key used to match an upload notice to a Media Library item.
*
* @param {string} filename - baseFile or WordPress-generated filename.
* @return {string|undefined} Sanitized cache key.
*/
const getCacheKey = filename => filename
// Match WordPress filename sanitization to ensure consistent cache keys.
?.replace( /[^a-zA-Z0-9._-]+/g, '-' )
// Collapse repeated dashes created by the previous replacement.
.replace( /-+/g, '-' )
// Remove leading/trailing dashes around the filename.
.replace( /^-|-$/g, '' )
.toLowerCase()

/**
* Remove the numeric suffix WordPress adds when a filename already exists.
*
* @param {string} filename - WordPress-generated filename.
* @return {string|undefined} Filename without the final WordPress suffix.
*/
const stripFilenameSuffix = filename => {
// Convert `video-3.mov` back to `video.mov` for one-time self-correction.
return filename?.replace( /-\d+(\.[^.]+)$/, '$1' )
}

/**
* Find a temporary upload notice for a Media Library attachment.
*
* @param {string} filename - Filename from the media model.
* @return {Object|null} Matching upload notice, if any.
*/
export const getCachedUploadNotice = filename => {
if ( ! filename ) {
return null
}

const cache = window.cimoUploadNoticeCache || {}

// Get the filename which may have a suffix, and try to find a notice for it first.
const cacheKey = getCacheKey( filename )
if ( cache[ cacheKey ] ) {
return cache[ cacheKey ]
}

// Extract the base filename without the suffix and check if there's a notice for it.
// If no cache with the base filename exists, then there's no notice for this upload at all.
const baseFileKey = getCacheKey( stripFilenameSuffix( filename ) )
const baseFileNotice = cache[ baseFileKey ]
if ( ! baseFileNotice ) {
return null
}

// Copy the notice to WordPress's final filename key.
// Also remove remainingMatches from the cache since it's only needed for the base filename to track when to remove itself.
cache[ cacheKey ] = {
...baseFileNotice,
}
delete cache[ cacheKey ].remainingMatches

// Keep the baseFile key only while more suffixed filenames still need it.
if ( baseFileNotice.remainingMatches > 1 ) {
baseFileNotice.remainingMatches--
} else {
// Remove the base fallback so older similar media cannot keep matching it.
delete cache[ baseFileKey ]
}

return cache[ cacheKey ]
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Cache a temporary upload notice message under a sanitized filename key.
*
* @param {string} filename - Browser File.name from the failed upload.
* @param {string} message - Notice message to display in the media manager.
*/
export const setCachedUploadNotice = ( filename, message ) => {
if ( ! filename ) {
return
}

// Keep notices in memory only; refreshing the page clears them.
if ( ! window.cimoUploadNoticeCache ) {
window.cimoUploadNoticeCache = {}
}

// Generate the cache key based on the filename, matching WordPress's sanitization.
// This will always generate the basename, and is not guaranteed to match the final WordPress filename,
// which may have a suffix added (`-1` or `-2`.).
const baseFileKey = getCacheKey( filename )
const cachedNotice = window.cimoUploadNoticeCache[ baseFileKey ]

window.cimoUploadNoticeCache[ baseFileKey ] = {
message,
// Count duplicate uploads with the same browser filename so
// each suffixed item can self-correct once.
remainingMatches: ( cachedNotice?.remainingMatches || 0 ) + 1,
}
}

/**
* Create and cache an upload notice from a converter result.
*
* @param {Object} result - Converter result.
* @return {string|null} Cached notice message when the result includes one.
*/
export const cacheConverterNotice = result => {
const file = result?.file
if ( ! file?.name ) {
return null
}

// The converter result decides whether a sidebar notice should be shown.
if ( ! result?.notice ) {
return null
}

// Store only the display message; matching metadata is added by the cache layer.
setCachedUploadNotice( file.name, result.notice )
return result.notice
}
Loading