diff -u b/core/modules/image/css/editors/image.css b/core/modules/image/css/editors/image.css --- b/core/modules/image/css/editors/image.css +++ b/core/modules/image/css/editors/image.css @@ -1,6 +1,11 @@ /** * @file - * Styles for the Image module's in-place editor. + * Functional styles for the Image module's in-place editor. + */ + +/** + * A minimum width/height is required so that users can drag and drop files + * onto small images. */ .quickedit-image-element { min-width: 200px; @@ -10,106 +15,38 @@ .quickedit-image-dropzone { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; position: absolute; top: 0; left: 0; width: 100%; height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgba(116, 183, 255, 0.8); - transition: background .2s; -} - -.quickedit-image-dropzone.hover { - background: rgba(116, 183, 255, 0.9); -} - -.quickedit-image-dropzone.error { - background: rgba(255, 52, 27, 0.81); -} - -.quickedit-image-dropzone * { - pointer-events: none; } .quickedit-image-icon { display: block; width: 50px; height: 50px; - margin: 0 0 10px 0; background-repeat: no-repeat; background-size: cover; - transition: margin .5s; -} - -.quickedit-image-dropzone.upload .quickedit-image-icon { - background-image: url('../../images/upload.svg'); -} - -.quickedit-image-dropzone.error .quickedit-image-icon { - background-image: url('../../images/error.svg'); -} - -.quickedit-image-dropzone.loading .quickedit-image-icon { - margin: -10px 0 20px 0; -} - -.quickedit-image-dropzone.loading .quickedit-image-icon::after { - display: block; - content: ""; - margin-left: -10px; - margin-top: -5px; - animation-duration: 2s; - animation-name: quickedit-image-spin; - animation-iteration-count: infinite; - animation-timing-function: linear; - width: 60px; - height: 60px; - border-style: solid; - border-radius: 35px; - border-width: 5px; - border-color: white transparent transparent transparent; -} - -@keyframes quickedit-image-spin { - 0% {transform: rotate(0deg);} - 50% {transform: rotate(180deg);} - 100% {transform: rotate(360deg);} -} - -.quickedit-image-text { - display: block; - text-align: center; - color: white; - font-family: "Droid sans", "Lucida Grande", sans-serif; - pointer-events: none; - font-size: 16px; - -webkit-user-select: none; } .quickedit-image-field-info { display: flex; align-items: center; justify-content: flex-end; - background: rgba(0, 0, 0, 0.05); - border-top: 1px solid #c5c5c5; - padding: 5px; } -.quickedit-image-field-info label, .quickedit-image-field-info input { - margin-right: 5px; -} - -.quickedit-image-field-info input:last-child { - margin-right: 0; -} - -.quickedit-image-errors .messages__wrapper { - margin: 0; - padding: 0; +.quickedit-image-text { + display: block; } -.quickedit-image-errors .messages--error { - box-shadow: none; +/** + * If we do not prevent pointer-events for child elements, our drag+drop events + * will not fire properly. This can lead to unintentional redirects if a file + * is dropped on a child element when a user intended to upload it. + */ +.quickedit-image-dropzone * { + pointer-events: none; } diff -u b/core/modules/image/image.install b/core/modules/image/image.install --- b/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -66,5 +66,5 @@ * Flush caches as we changed field formatter metadata. */ -function image_update_8001() { +function image_update_8201() { // Empty update to trigger a cache flush. } diff -u b/core/modules/image/image.libraries.yml b/core/modules/image/image.libraries.yml --- b/core/modules/image/image.libraries.yml +++ b/core/modules/image/image.libraries.yml @@ -12,4 +12,6 @@ component: css/editors/image.css: {} + theme: + css/editors/image.theme.css: {} dependencies: - quickedit/quickedit diff -u b/core/modules/image/images/error.svg b/core/modules/image/images/error.svg --- b/core/modules/image/images/error.svg +++ b/core/modules/image/images/error.svg @@ -4 +4 @@ - \ No newline at end of file + diff -u b/core/modules/image/js/editors/image.js b/core/modules/image/js/editors/image.js --- b/core/modules/image/js/editors/image.js +++ b/core/modules/image/js/editors/image.js @@ -7,6 +7,77 @@ 'use strict'; + /** + * Theme function for validation errors of the Image module's in-place + * editor. + * + * @param {object} settings + * Settings object used to construct the markup. + * @param {string} settings.errors + * Already escaped HTML representing error messages. + * + * @return {string} + * The corresponding HTML. + */ + Drupal.theme.quickeditImageErrors = _.template( + '
' + + ' <%= errors %>' + + '
' + ); + + /** + * Theme function for the dropzone element of the Image module's in-place + * editor. + * + * @param {object} settings + * Settings object used to construct the markup. + * @param {string} settings.text + * Text to display inline with the dropzone element. + * + * @return {string} + * The corresponding HTML. + */ + Drupal.theme.quickeditImageDropzone = _.template( + '
' + + ' ' + + ' <%- text %>' + + '
' + ); + + /** + * Theme function for the toolbar of the Image module's in-place editor. + * + * @param {object} settings + * Settings object used to construct the markup. + * @param {bool} settings.alt_field + * Whether or not the "Alt" field is enabled for this field. + * @param {bool} settings.alt_field_required + * Whether or not the "Alt" field is required for this field. + * @param {string} settings.alt + * The current value for the "Alt" field. + * @param {bool} settings.title_field + * Whether or not the "Title" field is enabled for this field. + * @param {bool} settings.title_field_required + * Whether or not the "Title" field is required for this field. + * @param {string} settings.title + * The current value for the "Title" field. + * + * @return {string} + * The corresponding HTML. + */ + Drupal.theme.quickeditImageToolbar = _.template( + '
' + + '<% if (alt_field) { %>' + + ' ' + + ' required<%} %>/>' + + '<% } %>' + + '<% if (title_field) { %>' + + ' ' + + ' required<%} %>/>' + + '<% } %>' + + '
' + ); + Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.image# */{ /** @@ -15,7 +86,7 @@ * @augments Drupal.quickedit.EditorView * * @param {object} options - * Options for the plain text editor. + * Options for the image editor. */ initialize: function (options) { Drupal.quickedit.EditorView.prototype.initialize.call(this, options); @@ -37,41 +108,6 @@ }, /** - * Template to display errors inline with the toolbar. - */ - template_errors: _.template( - '
' + - ' <%= errors %>' + - '
' - ), - - /** - * Template to display the dropzone area. - */ - template_dropzone: _.template( - '
' + - ' ' + - ' <%- text %>' + - '
' - ), - - /** - * Template to display the toolbar. - */ - template_toolbar: _.template( - '
' + - '<% if (alt_field) { %>' + - ' ' + - ' required<%} %>/>' + - '<% } %>' + - '<% if (title_field) { %>' + - ' ' + - ' required<%} %>/>' + - '<% } %>' + - '
' - ), - - /** * @inheritdoc * * @param {Drupal.quickedit.FieldModel} fieldModel @@ -136,11 +172,11 @@ $(this).removeClass('hover'); }); - $dropzone.on('drop', function (e){ + $dropzone.on('drop', function (e) { stopEvent(e); // Only respond when a file is dropped (could be another element). - if(e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length){ + if(e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) { $(this).removeClass('hover'); self.uploadImage(e.originalEvent.dataTransfer.files[0]); } @@ -167,7 +203,6 @@ this.removeValidationErrors(); } - // Before we submit, validate the alt/title text fields. this.save(options); break; @@ -223,10 +258,10 @@ * In addition to formatting the correct request, this also handles error * codes and messages by displaying them visually inline with the image. * - * Drupal.ajax is not called here as the Form API is unused by this editor, - * and our JSON requests/responses try to be editor-agnostic. Ideally - * similar logic and routes could be used by modules like CKEditor for - * drag+drop file uploads as well. + * Drupal.ajax is not called here as the Form API is unused by this + * in-place editor, and our JSON requests/responses try to be editor + * agnostic. Ideally similar logic and routes could be used by modules + * like CKEditor for drag+drop file uploads as well. * * @param {string} type * The type of request (i.e. GET, POST, PUT, DELETE, etc.) @@ -247,7 +282,7 @@ cache: false, contentType: false, processData: false, - success: function(response) { + success: function (response) { if (response.main_error) { this.renderDropzone('error', response.main_error); if (response.errors.length) { @@ -259,7 +294,7 @@ callback(response); } }, - error: function() { + error: function () { this.renderDropzone('error', Drupal.t('A server error has occurred.')); } }); @@ -280,7 +315,7 @@ var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode')); var self = this; self.ajax('GET', url, null, function (response) { - $toolbar = $(self.template_toolbar(response)); + $toolbar = $(Drupal.theme.quickeditImageToolbar(response)); $toolgroup.append($toolbar); $toolbar.on('keyup paste', function () { fieldModel.set('state', 'changed'); @@ -308,7 +343,7 @@ $dropzone.children('.quickedit-image-text').html(text); } else { - $dropzone = $(this.template_dropzone({ + $dropzone = $(Drupal.theme.quickeditImageDropzone({ state: state, text: text })); @@ -336,7 +371,7 @@ * @inheritdoc */ showValidationErrors: function () { - var $errors = $(this.template_errors({ + var $errors = $(Drupal.theme.quickeditImageErrors({ errors: this.model.get('validationErrors') })); $('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId()) diff -u b/core/themes/stable/css/image/editors/image.css b/core/themes/stable/css/image/editors/image.css --- b/core/themes/stable/css/image/editors/image.css +++ b/core/themes/stable/css/image/editors/image.css @@ -1,6 +1,11 @@ /** * @file - * Styles for the Image module's in-place editor. + * Functional styles for the Image module's in-place editor. + */ + +/** + * A minimum width/height is required so that users can drag and drop files + * onto small images. */ .quickedit-image-element { min-width: 200px; @@ -10,106 +15,38 @@ .quickedit-image-dropzone { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; position: absolute; top: 0; left: 0; width: 100%; height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgba(116, 183, 255, 0.8); - transition: background .2s; -} - -.quickedit-image-dropzone.hover { - background: rgba(116, 183, 255, 0.9); -} - -.quickedit-image-dropzone.error { - background: rgba(255, 52, 27, 0.81); -} - -.quickedit-image-dropzone * { - pointer-events: none; } .quickedit-image-icon { display: block; width: 50px; height: 50px; - margin: 0 0 10px 0; background-repeat: no-repeat; background-size: cover; - transition: margin .5s; -} - -.quickedit-image-dropzone.upload .quickedit-image-icon { - background-image: url('../../../images/image/upload.svg'); -} - -.quickedit-image-dropzone.error .quickedit-image-icon { - background-image: url('../../../images/image/error.svg'); -} - -.quickedit-image-dropzone.loading .quickedit-image-icon { - margin: -10px 0 20px 0; -} - -.quickedit-image-dropzone.loading .quickedit-image-icon::after { - display: block; - content: ""; - margin-left: -10px; - margin-top: -5px; - animation-duration: 2s; - animation-name: quickedit-image-spin; - animation-iteration-count: infinite; - animation-timing-function: linear; - width: 60px; - height: 60px; - border-style: solid; - border-radius: 35px; - border-width: 5px; - border-color: white transparent transparent transparent; -} - -@keyframes quickedit-image-spin { - 0% {transform: rotate(0deg);} - 50% {transform: rotate(180deg);} - 100% {transform: rotate(360deg);} -} - -.quickedit-image-text { - display: block; - text-align: center; - color: white; - font-family: "Droid sans", "Lucida Grande", sans-serif; - pointer-events: none; - font-size: 16px; - -webkit-user-select: none; } .quickedit-image-field-info { display: flex; align-items: center; justify-content: flex-end; - background: rgba(0, 0, 0, 0.05); - border-top: 1px solid #c5c5c5; - padding: 5px; } -.quickedit-image-field-info label, .quickedit-image-field-info input { - margin-right: 5px; -} - -.quickedit-image-field-info input:last-child { - margin-right: 0; -} - -.quickedit-image-errors .messages__wrapper { - margin: 0; - padding: 0; +.quickedit-image-text { + display: block; } -.quickedit-image-errors .messages--error { - box-shadow: none; +/** + * If we do not prevent pointer-events for child elements, our drag+drop events + * will not fire properly. This can lead to unintentional redirects if a file + * is dropped on a child element when a user intended to upload it. + */ +.quickedit-image-dropzone * { + pointer-events: none; } diff -u b/core/themes/stable/images/image/error.svg b/core/themes/stable/images/image/error.svg --- b/core/themes/stable/images/image/error.svg +++ b/core/themes/stable/images/image/error.svg @@ -4 +4 @@ - \ No newline at end of file + diff -u b/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml --- b/core/themes/stable/stable.info.yml +++ b/core/themes/stable/stable.info.yml @@ -99,9 +99,11 @@ theme: css/image.admin.css: css/image/image.admin.css image/quickedit.inPlaceEditor.image: - css: - component: - css/editors/image.css: css/image/editors/image.css + css: + component: + css/editors/image.css: css/image/editors/image.css + theme: + css/editors/image.theme.css: css/image/editors/image.theme.css language/drupal.language.admin: css: only in patch2: unchanged: --- /dev/null +++ b/core/modules/image/css/editors/image.theme.css @@ -0,0 +1,88 @@ +/** + * @file + * Theme styles for the Image module's in-place editor. + */ + +.quickedit-image-dropzone { + background: rgba(116, 183, 255, 0.8); + transition: background .2s; +} + +.quickedit-image-icon { + margin: 0 0 10px 0; + transition: margin .5s; +} + +.quickedit-image-dropzone.hover { + background: rgba(116, 183, 255, 0.9); +} + +.quickedit-image-dropzone.error { + background: rgba(255, 52, 27, 0.81); +} + +.quickedit-image-dropzone.upload .quickedit-image-icon { + background-image: url('../../images/upload.svg'); +} + +.quickedit-image-dropzone.error .quickedit-image-icon { + background-image: url('../../images/error.svg'); +} + +.quickedit-image-dropzone.loading .quickedit-image-icon { + margin: -10px 0 20px 0; +} + +.quickedit-image-dropzone.loading .quickedit-image-icon::after { + display: block; + content: ""; + margin-left: -10px; + margin-top: -5px; + animation-duration: 2s; + animation-name: quickedit-image-spin; + animation-iteration-count: infinite; + animation-timing-function: linear; + width: 60px; + height: 60px; + border-style: solid; + border-radius: 35px; + border-width: 5px; + border-color: white transparent transparent transparent; +} + +@keyframes quickedit-image-spin { + 0% {transform: rotate(0deg);} + 50% {transform: rotate(180deg);} + 100% {transform: rotate(360deg);} +} + +.quickedit-image-text { + text-align: center; + color: white; + font-family: "Droid sans", "Lucida Grande", sans-serif; + font-size: 16px; + -webkit-user-select: none; +} + +.quickedit-image-field-info { + background: rgba(0, 0, 0, 0.05); + border-top: 1px solid #c5c5c5; + padding: 5px; +} + +.quickedit-image-field-info label, .quickedit-image-field-info input { + margin-right: 5px; +} + +.quickedit-image-field-info input:last-child { + margin-right: 0; +} + +.quickedit-image-errors .messages__wrapper { + margin: 0; + padding: 0; +} + +.quickedit-image-errors .messages--error { + box-shadow: none; +} only in patch2: unchanged: --- /dev/null +++ b/core/themes/stable/css/image/editors/image.theme.css @@ -0,0 +1,88 @@ +/** + * @file + * Theme styles for the Image module's in-place editor. + */ + +.quickedit-image-dropzone { + background: rgba(116, 183, 255, 0.8); + transition: background .2s; +} + +.quickedit-image-icon { + margin: 0 0 10px 0; + transition: margin .5s; +} + +.quickedit-image-dropzone.hover { + background: rgba(116, 183, 255, 0.9); +} + +.quickedit-image-dropzone.error { + background: rgba(255, 52, 27, 0.81); +} + +.quickedit-image-dropzone.upload .quickedit-image-icon { + background-image: url('../../../images/image/upload.svg'); +} + +.quickedit-image-dropzone.error .quickedit-image-icon { + background-image: url('../../../images/image/error.svg'); +} + +.quickedit-image-dropzone.loading .quickedit-image-icon { + margin: -10px 0 20px 0; +} + +.quickedit-image-dropzone.loading .quickedit-image-icon::after { + display: block; + content: ""; + margin-left: -10px; + margin-top: -5px; + animation-duration: 2s; + animation-name: quickedit-image-spin; + animation-iteration-count: infinite; + animation-timing-function: linear; + width: 60px; + height: 60px; + border-style: solid; + border-radius: 35px; + border-width: 5px; + border-color: white transparent transparent transparent; +} + +@keyframes quickedit-image-spin { + 0% {transform: rotate(0deg);} + 50% {transform: rotate(180deg);} + 100% {transform: rotate(360deg);} +} + +.quickedit-image-text { + text-align: center; + color: white; + font-family: "Droid sans", "Lucida Grande", sans-serif; + font-size: 16px; + -webkit-user-select: none; +} + +.quickedit-image-field-info { + background: rgba(0, 0, 0, 0.05); + border-top: 1px solid #c5c5c5; + padding: 5px; +} + +.quickedit-image-field-info label, .quickedit-image-field-info input { + margin-right: 5px; +} + +.quickedit-image-field-info input:last-child { + margin-right: 0; +} + +.quickedit-image-errors .messages__wrapper { + margin: 0; + padding: 0; +} + +.quickedit-image-errors .messages--error { + box-shadow: none; +}