diff --git a/src/js/_enqueues/admin/site-icon.js b/src/js/_enqueues/admin/site-icon.js index 5b4bc28a2044f..ae59db5d4546f 100644 --- a/src/js/_enqueues/admin/site-icon.js +++ b/src/js/_enqueues/admin/site-icon.js @@ -1,15 +1,40 @@ -(function($) { - var frame; - - function calculateImageSelectOptions ( attachment ) { - var realWidth = attachment.get( 'width' ), +/** + * Handle the site icon setting in options-general.php. + * + * @since 6.5.0 + * @output wp-admin/js/site-icon.js + */ + +/* global jQuery, wp */ + +( function ( $ ) { + var $chooseButton = $( '#choose-from-library-button' ), + $iconPreview = $( '#site-icon-preview' ), + $browserIconPreview = $( '#browser-icon-preview' ), + $appIconPreview = $( '#app-icon-preview' ), + $hiddenDataField = $( '#site_icon_hidden_field' ), + $removeButton = $( '#js-remove-site-icon' ), + frame; + + /** + * Calculate image selection options based on the attachment dimensions. + * + * @since 6.5.0 + * + * @param {Object} attachment The attachment object representing the image. + * @return {Object} The image selection options. + */ + function calculateImageSelectOptions( attachment ) { + var realWidth = attachment.get( 'width' ), realHeight = attachment.get( 'height' ), xInit = 512, yInit = 512, ratio = xInit / yInit, - xImg = xInit, - yImg = yInit, - x1, y1, imgSelectOptions; + xImg = xInit, + yImg = yInit, + x1, + y1, + imgSelectOptions; if ( realWidth / realHeight > ratio ) { yInit = realHeight; @@ -35,104 +60,192 @@ x1: x1, y1: y1, x2: xInit + x1, - y2: yInit + y1 + y2: yInit + y1, }; return imgSelectOptions; } - $( function() { - // Build the choose from library frame. - $( '#choose-from-library-link' ).on( 'click', function() { - var $el = $(this); - - // Create the media frame. - frame = wp.media({ - button: { - // Set the text of the button. - text: $el.data('update'), - // Don't close, we might need to crop. - close: false - }, - states: [ - new wp.media.controller.Library({ - title: $el.data( 'choose' ), - library: wp.media.query({ type: 'image' }), - date: false, - suggestedWidth: $el.data( 'size' ), - suggestedHeight: $el.data( 'size' ) - }), - new wp.media.controller.SiteIconCropper({ - control: { - params: { - width: $el.data( 'size' ), - height: $el.data( 'size' ) - } + /** + * Initializes the media frame for selecting or cropping an image. + * + * @since 6.5.0 + */ + $chooseButton.on( 'click', function () { + var $el = $( this ); + + // Create the media frame. + frame = wp.media( { + button: { + // Set the text of the button. + text: $el.data( 'update' ), + + // Don't close, we might need to crop. + close: false, + }, + states: [ + new wp.media.controller.Library( { + title: $el.data( 'choose-text' ), + library: wp.media.query( { type: 'image' } ), + date: false, + suggestedWidth: $el.data( 'size' ), + suggestedHeight: $el.data( 'size' ), + } ), + new wp.media.controller.SiteIconCropper( { + control: { + params: { + width: $el.data( 'size' ), + height: $el.data( 'size' ), }, - imgSelectOptions: calculateImageSelectOptions - }) - ] - }); - - frame.on( 'cropped', function( attachment) { - $( '#site_icon_hidden_field' ).val(attachment.id); - switchToUpdate(attachment.url); + }, + imgSelectOptions: calculateImageSelectOptions, + } ), + ], + } ); + + frame.on( 'cropped', function ( attachment ) { + $hiddenDataField.val( attachment.id ); + switchToUpdate( attachment ); + frame.close(); + + // Start over with a frame that is so fresh and so clean clean. + frame = null; + } ); + + // When an image is selected, run a callback. + frame.on( 'select', function () { + // Grab the selected attachment. + var attachment = frame.state().get( 'selection' ).first(); + + if ( + attachment.attributes.height === $el.data( 'size' ) && + $el.data( 'size' ) === attachment.attributes.width + ) { + switchToUpdate( attachment.attributes ); frame.close(); - // Start over with a frame that is so fresh and so clean clean. - frame = null; - }); - - // When an image is selected, run a callback. - frame.on( 'select', function() { - // Grab the selected attachment. - var attachment = frame.state().get('selection').first(); - - if ( attachment.attributes.height === $el.data('size') && $el.data('size') === attachment.attributes.width ) { - // Set the value of the hidden input to the attachment id. - $( '#site_icon_hidden_field').val(attachment.id); - switchToUpdate(attachment.attributes.url); - frame.close(); - } else { - frame.setState( 'cropper' ); - } - }); - - frame.open(); - }); - }); - - function switchToUpdate( url ){ - // Set site-icon-img src to the url and remove the hidden class. - $( '#site-icon-preview').find('img').not('.browser-preview').each( function(i, img ){ - $(img).attr('src', url ); - }); - $( '#site-icon-preview' ).removeClass( 'hidden' ); - // Remove hidden class from remove. - $( '#js-remove-site-icon' ).removeClass( 'hidden' ); - // If the button is not in the update state, swap the classes. - if( $( '#choose-from-library-link' ).attr( 'data-state' ) !== '1' ){ - var classes = $( '#choose-from-library-link' ).attr( 'class' ); - $( '#choose-from-library-link' ).attr( 'class', $( '#choose-from-library-link' ).attr('data-alt-classes') ); - $( '#choose-from-library-link' ).attr( 'data-alt-classes', classes ); - $( '#choose-from-library-link' ).attr( 'data-state', '1' ); + + // Set the value of the hidden input to the attachment id. + $hiddenDataField.val( attachment.id ); + } else { + frame.setState( 'cropper' ); + } + } ); + + frame.open(); + } ); + + /** + * Update the UI when a site icon is selected. + * + * @since 6.5.0 + * + * @param {array} attributes The attributes for the attachment. + */ + function switchToUpdate( attributes ) { + var i18nAppAlternativeString, i18nBrowserAlternativeString; + + if ( attributes.alt ) { + i18nAppAlternativeString = wp.i18n.sprintf( + /* translators: %s: The selected image alt text. */ + wp.i18n.__( 'App icon preview: Current image: %s' ), + attributes.alt + ); + i18nBrowserAlternativeString = wp.i18n.sprintf( + /* translators: %s: The selected image alt text. */ + wp.i18n.__( 'Browser icon preview: Current image: %s' ), + attributes.alt + ); + } else { + i18nAppAlternativeString = wp.i18n.sprintf( + /* Translators: %s: The selected image filename. */ + wp.i18n.__( + 'App icon preview: The current image has no alternative text. The file name is: %s' + ), + attributes.filename + ); + i18nBrowserAlternativeString = wp.i18n.sprintf( + /* Translators: %s: The selected image filename. */ + wp.i18n.__( + 'Browser icon preview: The current image has no alternative text. The file name is: %s' + ), + attributes.filename + ); } - // swap the text of the button - $( '#choose-from-library-link' ).text( $( '#choose-from-library-link' ).attr( 'data-update-text' ) ); + // Set site-icon-img src and alternative text to app icon preview. + $appIconPreview.attr( { + src: attributes.url, + alt: i18nAppAlternativeString, + } ); + + // Set site-icon-img src and alternative text to browser preview. + $browserIconPreview.attr( { + src: attributes.url, + alt: i18nBrowserAlternativeString, + } ); + + // Remove hidden class from icon preview div and remove button. + $iconPreview.removeClass( 'hidden' ); + $removeButton.removeClass( 'hidden' ); + + // Set the global CSS variable for --site-icon-url to the selected image URL. + document.documentElement.style.setProperty( + '--site-icon-url', + 'url(' + attributes.url + ')' + ); + + // If the choose button is not in the update state, swap the classes. + if ( $chooseButton.attr( 'data-state' ) !== '1' ) { + $chooseButton.attr( { + class: $chooseButton.attr( 'data-alt-classes' ), + 'data-alt-classes': $chooseButton.attr( 'class' ), + 'data-state': '1', + } ); + } + + // Swap the text of the choose button. + $chooseButton.text( $chooseButton.attr( 'data-update-text' ) ); } - $( '#js-remove-site-icon' ).on( 'click', function() { - $( '#site_icon_hidden_field' ).val( 'false' ); - $( '#site-icon-preview' ).toggleClass( 'hidden' ); + /** + * Handles the click event of the remove button. + * + * @since 6.5.0 + */ + $removeButton.on( 'click', function () { + $hiddenDataField.val( 'false' ); $( this ).toggleClass( 'hidden' ); - - var classes = $( '#choose-from-library-link' ).attr( 'class' ); - $( '#choose-from-library-link' ).attr( 'class', $( '#choose-from-library-link' ).attr( 'data-alt-classes' ) ); - $( '#choose-from-library-link' ).attr( 'data-alt-classes', classes ); - - // Swap the text of the button. - $( '#choose-from-library-link' ).text( $( '#choose-from-library-link' ).attr( 'data-choose-text' ) ); - // Set the state of the button so it can be changed on new icon. - $( '#choose-from-library-link' ).attr( 'data-state', ''); - }); -}(jQuery)); + $iconPreview.toggleClass( 'hidden' ); + $browserIconPreview.attr( { + src: '', + alt: '', + } ); + $appIconPreview.attr( { + src: '', + alt: '', + } ); + + /** + * Resets state to the button, for correct visual style and state. + * Updates the text of the button. + * Sets focus state to the button. + */ + $chooseButton + .attr( { + class: $chooseButton.attr( 'data-alt-classes' ), + 'data-alt-classes': $chooseButton.attr( 'class' ), + 'data-state': '', + } ) + .text( $chooseButton.attr( 'data-choose-text' ) ) + .trigger( 'focus' ); + } ); + + /** + * Update the site icon preview when the site title changes. + * + * @since 6.5.0 + */ + $( '#blogname' ).on( 'input', function () { + $( '#site-icon-preview-site-title' ).text( $( this ).val() ); + } ); +} )( jQuery ); diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index db76d6cb05e7a..63eb3e88eb6ee 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -789,7 +789,7 @@ ul#add-to-blog-users { outline: 2px solid transparent; } -.button-add-site-icon{ +.button-add-site-icon { width: 100%; cursor: pointer; text-align: center; @@ -801,23 +801,23 @@ ul#add-to-blog-users { } .button-add-site-icon:focus, -.button-add-site-icon:hover{ - background: white; +.button-add-site-icon:hover { + background: #fff; } -.site-icon-section .favicon-preview{ +.site-icon-section .favicon-preview { float: left; } -.site-icon-section .app-icon-preview{ +.site-icon-section .app-icon-preview { float: left; margin: 0 20px; } -.site-icon-section .site-icon-preview img{ +.site-icon-section .site-icon-preview img { max-width: 100%; } -.button-ad-site-icon:focus{ +.button-add-site-icon:focus { background-color: #fff; border-color: #3582c4; border-style: solid; diff --git a/src/wp-admin/css/site-icon.css b/src/wp-admin/css/site-icon.css index 5660e9a39e81c..6adc3437b7e0e 100644 --- a/src/wp-admin/css/site-icon.css +++ b/src/wp-admin/css/site-icon.css @@ -7,7 +7,6 @@ overflow: hidden; position: relative; max-width: 180px; - float: left; } .site-icon-preview .favicon, @@ -75,3 +74,113 @@ flex-wrap: wrap; gap: 10px; } + +.site-icon-preview-inline { + display: flex; + height: 60px; + padding: 8px 0 0 8px; + align-items: flex-start; + gap: 8px; + position: relative; + overflow: hidden; + box-sizing: border-box; +} + +.site-icon-preview-inline.hidden { + display: none; +} + +.site-icon-preview-inline.customizer { + width: 275px; +} + +.site-icon-preview-inline.settings { + width: 350px; + padding: 16px 0 0 16px; + gap: 16px; + height: 88px; + margin-bottom: 20px; + border: 1px solid #8c8f94; + border-radius: 4px; +} + +.site-icon-preview-inline.settings .home-icon { + width: 58px; + height: 58px; + border-radius: 12.95px; +} + +.site-icon-preview-inline.settings:after { + background: var(--site-icon-url); +} + +.site-icon-preview-inline:after { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + width: 200%; + height: 200%; + transform: translateX(-25%) translateY(-25%); + filter: blur(6px); + opacity: 0.5; +} + +.home-icon { + width: 44px; + height: 44px; + flex-shrink: 0; + z-index: 1; + border-radius: 10px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.3); +} + +.site-icon-preview-browser { + display: flex; + height: 100%; + padding: 4px 4px 4px 12px; + align-items: flex-start; + gap: 16px; + flex: 1 0 0; + z-index: 1; + border-radius: 10px 0 0 0; + border-top: 1px solid rgba(255, 255, 255, 0.2); + border-left: 1px solid rgba(255, 255, 255, 0.2); + background: linear-gradient(180deg, #dcdcde 0%, #bdbdbd 100%); + box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.4); +} + +.site-icon-preview-browser-buttons { + fill: #8c8f94; +} + +.site-icon-preview-tab { + display: flex; + padding: 8px; + align-items: center; + gap: 8px; + flex: 1 0 0; + border-radius: 4px; + background: #fff; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); +} + +.site-icon-preview-tab .site-icon { + width: 24px; + height: 24px; +} + +.site-icon-preview-close-button { + fill: #50575e; +} + +.site-icon-preview-site-title { + width: 51px; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1 0 0; + overflow: hidden; + font-size: 14px; + font-weight: 500; +} diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 56020d533fb9e..97b3e94475108 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -107,34 +107,78 @@ $classes_for_upload_button = 'upload-button button-add-media button-add-site-icon'; $classes_for_update_button = 'button'; + $classes_for_wrapper = ''; - $classes_for_avatar = 'avatar avatar-150'; if ( has_site_icon() ) { - $classes_for_avatar .= ' has-site-icon'; + $classes_for_wrapper .= ' has-site-icon'; $classes_for_button = $classes_for_update_button; $classes_for_button_on_change = $classes_for_upload_button; } else { - $classes_for_avatar .= ' hidden'; + $classes_for_wrapper .= ' hidden'; $classes_for_button = $classes_for_upload_button; $classes_for_button_on_change = $classes_for_update_button; } + // Handle alt text for site icon on page load. + $site_icon_id = (int) get_option( 'site_icon' ); + $app_icon_alt_value = ''; + $browser_icon_alt_value = ''; + + if ( $site_icon_id ) { + $img_alt = get_post_meta( $site_icon_id, '_wp_attachment_image_alt', true ); + $filename = wp_basename( get_site_icon_url() ); + $app_icon_alt_value = sprintf( + /* Translators: %s: The selected image filename. */ + __( 'App icon preview: The current image has no alternative text. The file name is: %s' ), + $filename + ); + + $browser_icon_alt_value = sprintf( + /* Translators: %s: The selected image filename. */ + __( 'Browser icon preview: The current image has no alternative text. The file name is: %s' ), + $filename + ); + if ( $img_alt ) { + $app_icon_alt_value = sprintf( + /* Translators: %s: The selected image alt text. */ + __( 'App icon preview: Current image: %s' ), + $img_alt + ); + + $browser_icon_alt_value = sprintf( + /* Translators: %s: The selected image alt text. */ + __( 'Browser icon preview: Current image: %s' ), + $img_alt + ); + } + } ?> -
-
- -
- <?php esc_attr_e( 'Preview as a browser icon' ); ?> -
- + + + +
+ <?php echo esc_attr( $app_icon_alt_value ); ?> +
+ +
+ <?php echo esc_attr( $browser_icon_alt_value ); ?> + +
- <?php esc_attr_e( 'Preview as an app icon' ); ?>
+
+
-

- -

512 × 512' ); + printf( __( 'Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. They should be square and at least %s pixels.' ), '512 × 512' ); ?>

diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index dca355a0a7bce..dccc0d97a0e5f 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -849,7 +849,8 @@ function wp_default_scripts( $scripts ) { $scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 ); - $scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery', 'jcrop' ), false, 1 ); + $scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery' ), false, 1 ); + $scripts->set_translations( 'site-icon' ); // WordPress no longer uses or bundles Prototype or script.aculo.us. These are now pulled from an external source. $scripts->add( 'prototype', 'https://ajax.googleapis.com/ajax/libs/prototype/1.7.1.0/prototype.js', array(), '1.7.1' );