Customize: Add edit shortcuts in customizer preview to visually expose editable elements and focus on the corresponding controls when clicked.
* Edit shortcuts show initially for a moment and then fade away so as to not get in the way of the preview. * Visibility of edit shortcuts is toggled by clicking/touching anywhere inert in the document. * Implements UI for mobile and touch devices which do not support shift-click. * Adds `editShortcutVisibility` state. * Adds new methods to `wp.customize.selectiveRefresh.Partial` for managing edit shortcuts. Incorporates aspects of the Customize Direct Manipulation feature plugin. Props sirbrillig, mattwiebe, celloexpressions, melchoyce, westonruter, afercia. Fixes #27403. Built from https://develop.svn.wordpress.org/trunk@38967 git-svn-id: http://core.svn.wordpress.org/trunk@38910 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
@@ -357,7 +357,6 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
|
||||
widgetPartial = new self.WidgetPartial( partialId, {
|
||||
params: {}
|
||||
} );
|
||||
api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
|
||||
}
|
||||
|
||||
// Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
|
||||
@@ -400,6 +399,8 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
|
||||
wasInserted = true;
|
||||
} );
|
||||
|
||||
api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
|
||||
|
||||
if ( wasInserted ) {
|
||||
sidebarPartial.reflowWidgets();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
|
||||
self = {
|
||||
ready: $.Deferred(),
|
||||
editShortcutVisibility: new api.Value(),
|
||||
data: {
|
||||
partials: {},
|
||||
renderQueryVar: '',
|
||||
@@ -42,7 +43,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
|
||||
id: null,
|
||||
|
||||
/**
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.5.0
|
||||
@@ -82,8 +83,9 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
*/
|
||||
ready: function() {
|
||||
var partial = this;
|
||||
_.each( _.pluck( partial.placements(), 'container' ), function( container ) {
|
||||
$( container ).attr( 'title', self.data.l10n.shiftClickToEdit );
|
||||
_.each( partial.placements(), function( placement ) {
|
||||
$( placement.container ).attr( 'title', self.data.l10n.shiftClickToEdit );
|
||||
partial.createEditShortcutForPlacement( placement );
|
||||
} );
|
||||
$( document ).on( 'click', partial.params.selector, function( e ) {
|
||||
if ( ! e.shiftKey ) {
|
||||
@@ -98,6 +100,181 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Create and show the edit shortcut for a given partial placement container.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param {Placement} placement The placement container element.
|
||||
* @returns {void}
|
||||
*/
|
||||
createEditShortcutForPlacement: function( placement ) {
|
||||
var partial = this, $shortcut, $placementContainer;
|
||||
if ( ! placement.container ) {
|
||||
return;
|
||||
}
|
||||
$placementContainer = $( placement.container );
|
||||
if ( ! $placementContainer.length ) {
|
||||
return;
|
||||
}
|
||||
$shortcut = partial.createEditShortcut();
|
||||
partial.positionEditShortcut( placement, $shortcut );
|
||||
$shortcut.on( 'click', function( event ) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
partial.showControl();
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Position an edit shortcut for the placement container.
|
||||
*
|
||||
* The shortcut must already be created and added to the page.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param {Placement} placement The placement for the partial.
|
||||
* @param {jQuery} $editShortcut The shortcut element as a jQuery object.
|
||||
* @returns {void}
|
||||
*/
|
||||
positionEditShortcut: function( placement, $editShortcut ) {
|
||||
var partial = this, $placementContainer;
|
||||
$placementContainer = $( placement.container );
|
||||
$placementContainer.prepend( $editShortcut );
|
||||
if ( 'absolute' === $placementContainer.css( 'position' ) ) {
|
||||
$editShortcut.addClass( 'customize-partial-edit-shortcut-absolute' );
|
||||
$editShortcut.css( partial.getEditShortcutPositionStyles( $placementContainer ) );
|
||||
partial.whenPageChanges( function() {
|
||||
$editShortcut.css( partial.getEditShortcutPositionStyles( $placementContainer ) );
|
||||
} );
|
||||
}
|
||||
if ( ! $placementContainer.is( ':visible' ) || 'none' === $placementContainer.css( 'display' ) ) {
|
||||
$editShortcut.addClass( 'customize-partial-edit-shortcut-hidden' );
|
||||
}
|
||||
$editShortcut.toggleClass( 'customize-partial-edit-shortcut-left-margin', $editShortcut.offset().left < 1 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Call a callback function when the page changes.
|
||||
*
|
||||
* This calls a callback for any change that might require refreshing the edit shortcuts.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param {Function} callback The function to call when the page changes.
|
||||
* @returns {void}
|
||||
*/
|
||||
whenPageChanges: function( callback ) {
|
||||
var debouncedCallback, $document;
|
||||
debouncedCallback = _.debounce( function() {
|
||||
// Timeout allows any page animations to finish
|
||||
setTimeout( callback, 100 );
|
||||
}, 350 );
|
||||
// When window is resized.
|
||||
$( window ).resize( debouncedCallback );
|
||||
// When any customizer setting changes.
|
||||
api.bind( 'change', debouncedCallback );
|
||||
$document = $( window.document );
|
||||
// After scroll in case there are fixed position elements
|
||||
$document.on( 'scroll', debouncedCallback );
|
||||
// After page click (eg: hamburger menus)
|
||||
$document.on( 'click', debouncedCallback );
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the CSS positioning for the edit shortcut for a given partial placement.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param {jQuery} $placementContainer The placement container element as a jQuery object.
|
||||
* @return {Object} Object containing CSS positions.
|
||||
*/
|
||||
getEditShortcutPositionStyles: function( $placementContainer ) {
|
||||
return {
|
||||
top: $placementContainer.css( 'top' ),
|
||||
left: $placementContainer.css( 'left' ),
|
||||
right: 'auto'
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the unique class name for the edit shortcut button for this partial.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @return {string} Partial ID converted into a class name for use in shortcut.
|
||||
*/
|
||||
getEditShortcutClassName: function() {
|
||||
var partial = this, cleanId;
|
||||
cleanId = partial.id.replace( /]/g, '' ).replace( /\[/g, '-' );
|
||||
return 'customize-partial-edit-shortcut-' + cleanId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the appropriate translated string for the edit shortcut button.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @return {string} Tooltip for edit shortcut.
|
||||
*/
|
||||
getEditShortcutTitle: function() {
|
||||
var partial = this, l10n = self.data.l10n;
|
||||
switch ( partial.getType() ) {
|
||||
case 'widget':
|
||||
return l10n.clickEditWidget;
|
||||
case 'blogname':
|
||||
return l10n.clickEditTitle;
|
||||
case 'blogdescription':
|
||||
return l10n.clickEditTitle;
|
||||
case 'nav_menu':
|
||||
return l10n.clickEditMenu;
|
||||
default:
|
||||
return l10n.clickEditMisc;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the type of this partial
|
||||
*
|
||||
* Will use `params.type` if set, but otherwise will try to infer type from settingId.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @return {string} Type of partial derived from type param or the related setting ID.
|
||||
*/
|
||||
getType: function() {
|
||||
var partial = this, settingId;
|
||||
settingId = partial.params.primarySetting || _.first( partial.settings() ) || 'unknown';
|
||||
if ( partial.params.type ) {
|
||||
return partial.params.type;
|
||||
}
|
||||
if ( settingId.match( /^nav_menu_instance\[/ ) ) {
|
||||
return 'nav_menu';
|
||||
}
|
||||
if ( settingId.match( /^widget_.+\[\d+]$/ ) ) {
|
||||
return 'widget';
|
||||
}
|
||||
return settingId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an edit shortcut button for this partial.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @return {jQuery} The edit shortcut button element.
|
||||
*/
|
||||
createEditShortcut: function() {
|
||||
var partial = this, shortcutTitle;
|
||||
shortcutTitle = partial.getEditShortcutTitle();
|
||||
return $( '<button>', {
|
||||
'aria-label': shortcutTitle,
|
||||
'title': shortcutTitle,
|
||||
'type': 'button',
|
||||
'class': 'customize-partial-edit-shortcut ' + partial.getEditShortcutClassName()
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Find all placements for this partial int he document.
|
||||
*
|
||||
@@ -175,10 +352,16 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
* @since 4.5.0
|
||||
*/
|
||||
showControl: function() {
|
||||
var partial = this, settingId = partial.params.primarySetting;
|
||||
var partial = this, settingId = partial.params.primarySetting, menuSlug;
|
||||
if ( ! settingId ) {
|
||||
settingId = _.first( partial.settings() );
|
||||
}
|
||||
if ( partial.getType() === 'nav_menu' ) {
|
||||
menuSlug = partial.params.navMenuArgs.theme_location;
|
||||
if ( menuSlug ) {
|
||||
settingId = 'nav_menu_locations[' + menuSlug + ']';
|
||||
}
|
||||
}
|
||||
api.preview.send( 'focus-control-for-setting', settingId );
|
||||
},
|
||||
|
||||
@@ -319,6 +502,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
self.orginalDocumentWrite = null;
|
||||
/* jshint ignore:end */
|
||||
|
||||
partial.createEditShortcutForPlacement( placement );
|
||||
placement.container.removeClass( 'customize-partial-refreshing' );
|
||||
|
||||
// Prevent placement container from being being re-triggered as being rendered among nested partials.
|
||||
@@ -854,7 +1038,31 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
}
|
||||
} );
|
||||
|
||||
api.preview.bind( 'edit-shortcut-visibility', function( visibility ) {
|
||||
api.selectiveRefresh.editShortcutVisibility.set( visibility );
|
||||
} );
|
||||
api.selectiveRefresh.editShortcutVisibility.bind( function( visibility ) {
|
||||
var body = $( document.body ), shouldAnimateHide;
|
||||
|
||||
shouldAnimateHide = ( 'hidden' === visibility && body.hasClass( 'customize-partial-edit-shortcuts-shown' ) && ! body.hasClass( 'customize-partial-edit-shortcuts-hidden' ) );
|
||||
body.toggleClass( 'customize-partial-edit-shortcuts-hidden', shouldAnimateHide );
|
||||
body.toggleClass( 'customize-partial-edit-shortcuts-shown', 'visible' === visibility );
|
||||
body.toggleClass( 'customize-partial-edit-shortcuts-flash', 'initial' === visibility );
|
||||
} );
|
||||
|
||||
api.preview.bind( 'active', function() {
|
||||
var body = $( document.body ), buttonText, $editShortcutVisibilityButton;
|
||||
|
||||
// Add invisible button to toggle editShortcutVisibility.
|
||||
if ( $( '.edit-shortcut-visibility-button' ).length < 1 ) {
|
||||
buttonText = self.data.l10n.editShortcutVisibilityToggle || 'Toggle edit shortcuts';
|
||||
$editShortcutVisibilityButton = $( '<button type="button" class="edit-shortcut-visibility-button screen-reader-text"></button>' );
|
||||
$editShortcutVisibilityButton.text( buttonText );
|
||||
$editShortcutVisibilityButton.on( 'click', function() {
|
||||
api.selectiveRefresh.editShortcutVisibility.set( 'visible' === api.selectiveRefresh.editShortcutVisibility.get() ? 'hidden' : 'visible' );
|
||||
} );
|
||||
body.prepend( $editShortcutVisibilityButton );
|
||||
}
|
||||
|
||||
// Make all partials ready.
|
||||
self.partial.each( function( partial ) {
|
||||
@@ -865,6 +1073,14 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
|
||||
self.partial.bind( 'add', function( partial ) {
|
||||
partial.deferred.ready.resolve();
|
||||
} );
|
||||
|
||||
body.on( 'click', function( event ) {
|
||||
if ( $( event.target ).is( '.customize-partial-edit-shortcut, :input, a[href]' ) || 0 !== $( event.target ).closest( 'a' ).length ) {
|
||||
return; // Don't toggle shortcuts on form, link, or link child clicks.
|
||||
}
|
||||
api.selectiveRefresh.editShortcutVisibility.set( 'visible' === api.selectiveRefresh.editShortcutVisibility.get() ? 'hidden' : 'visible' );
|
||||
api.preview.send( 'edit-shortcut-visibility', api.selectiveRefresh.editShortcutVisibility.get() );
|
||||
} );
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user