diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index a3b8868cd0..b124da3b8e 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -149,6 +149,12 @@ function template_preprocess_file_upload_help(&$variables) { if (!empty($description)) { $descriptions[] = FieldFilteredMarkup::create($description); } + if (isset($upload_validators['file_validate_extensions'])) { + $descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['file_validate_extensions'][0]]); + } + if (isset($upload_validators['file_validate_size'])) { + $descriptions[] = t('Up to @size.', ['@size' => format_size($upload_validators['file_validate_size'][0])]); + } if (isset($cardinality)) { if ($cardinality == -1) { $descriptions[] = t('Unlimited number of files can be uploaded to this field.'); @@ -157,12 +163,6 @@ function template_preprocess_file_upload_help(&$variables) { $descriptions[] = \Drupal::translation()->formatPlural($cardinality, 'One file only.', 'Maximum @count files.'); } } - if (isset($upload_validators['file_validate_size'])) { - $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]); - } - if (isset($upload_validators['file_validate_extensions'])) { - $descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['file_validate_extensions'][0]]); - } if (isset($upload_validators['file_validate_image_resolution'])) { $max = $upload_validators['file_validate_image_resolution'][0]; diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index 1003bb094c..ada5b031b6 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -186,11 +186,10 @@ public static function uploadAjaxCallback(&$form, FormStateInterface &$form_stat $current_file_count = $form_state->get('file_upload_delta_initial'); if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; - $form[$current_file_count]['#attributes']['class'][] = 'managed-file'; } // Otherwise just add the new content class on a placeholder. else { - $form['#suffix'] .= ''; + $form['#suffix'] .= ''; } $status_messages = ['#type' => 'status_messages']; diff --git a/core/themes/seven/css/components/dropzone.css b/core/themes/seven/css/components/dropzone.css index 1b76b34e11..a831042f0a 100644 --- a/core/themes/seven/css/components/dropzone.css +++ b/core/themes/seven/css/components/dropzone.css @@ -1,369 +1,170 @@ /** - * Field Widgets. + * Dropzone. */ -.field--widget-image-image, -.field--widget-file-generic, -.field--widget-image-image td, -.field--widget-file-generic td { +.dropzone { position: relative; + display: flex; + width: 100%; + height: 100%; + min-height: 100px; + border: 1px solid #bfbfbf; + border-radius: 2px; + background: #fcfcfa; } -/** - * Remove Button. - */ -.field--widget-image-image .remove-button, -.field--widget-file-generic .remove-button, -#edit-settings-default-image .remove-button { - position: absolute; - top: 10px; - right: 10px; - text-align: right; - line-height: 17px; - text-transform: lowercase; - min-width: 95px; - font-weight: normal; - background: url(../../../../misc/icons/787878/ex.svg) right 0 no-repeat; - border: 0; - margin: 0; - padding: 0 1.5em; - float: right; - width: auto; - text-shadow: 0; -} - -[dir="rtl"] .field--widget-image-image .remove-button, -[dir="rtl"] .field--widget-file-generic .remove-button, -[dir="rtl"] #edit-settings-default-image .remove-button { - margin: 0 0 0 10px; - float: left; - text-align: left; - background-position: left 0; -} - -.field--widget-image-image .remove-button:focus, -.field--widget-file-generic .remove-button:focus, -#edit-settings-default-image .remove-button:focus { - box-shadow: none; - outline: none; -} - -.field--widget-image-image .remove-button:hover, -.field--widget-file-generic .remove-button:hover, -#edit-settings-default-image .remove-button:hover { - background: url(../../../../misc/icons/ee0000/ex.svg) right 0 no-repeat; - border: 0; - box-shadow: none; - color: #ee0000; -} - -[dir="rtl"] .field--widget-image-image .remove-button:hover, -[dir="rtl"] .field--widget-file-generic .remove-button:hover, -[dir="rtl"] #edit-settings-default-image .remove-button:hover { - background-position: left 0; -} - -/** - * Remove Buttons Ajax Throbber. - */ -.field--widget-image-image .remove-button + .ajax-progress, -.field--widget-file-generic .remove-button + .ajax-progress, -#edit-settings-default-image .remove-button + .ajax-progress { - position: absolute; - right: 85px; - top: 8px; +/* Dropzone trigger area. */ +.dropzone .dropzone-trigger { + flex: 0 0 100px; + min-height: 100px; + border-right: 1px solid #bfbfbf; + background: url("../../../../misc/icons/bebebe/dropzone-new.svg") 0 center no-repeat; } - -[dir="rtl"] .field--widget-image-image .remove-button + .ajax-progress, -[dir="rtl"] .field--widget-file-generic .remove-button + .ajax-progress, -[dir="rtl"] #edit-settings-default-image .remove-button + .ajax-progress { - right: auto; - left: 85px; +[dir="rtl"] .dropzone .dropzone-trigger { + border-right: none; + border-left: 1px solid #bfbfbf; } -/** - * Upload Buttons Ajax Throbber. - */ -.field--widget-image-image .upload-button + .ajax-progress, -.field--widget-file-generic .upload-button + .ajax-progress, -#edit-settings-default-image .upload-button + .ajax-progress { +.dropzone .js-form-file { position: absolute; - left: 50px; - top: 50%; - margin: -10px 0 0 -13px; + top: 0; + left: -100px; + display: inline-block; + width: 100px; + height: 100%; + min-height: 100px; + cursor: pointer; + opacity: 0; } - -[dir="rtl"] .field--widget-image-image .upload-button + .ajax-progress, -[dir="rtl"] .field--widget-file-generic .upload-button + .ajax-progress, -[dir="rtl"] #edit-settings-default-image .upload-button + .ajax-progress { +[dir="rtl"] .dropzone .js-form-file { + right: -100px; left: auto; - right: 50px; - margin: -10px -13px 0 0; -} - -/** - * Image Widget. - */ -.field--widget-image-image .dropzone-trigger, -#edit-settings-default-image .dropzone-trigger { - display: none; } -.field--widget-image-image .image-widget-data .form-item { - width: 100%; - float: left; -} - -.field--widget-image-image .image-preview img { - border: 0; - max-width: 100px; - height: auto; - padding: 0 10px 0 0; - float: left; -} - -[dir="rtl"] .field--widget-image-image .image-preview img { - float: right; - margin: 0 0 0 10px; -} - -/** - * Multi-Image Uploads Table. - */ -.dropzone-enabled table { - margin: 10px 0; -} -.dropzone-enabled table tr td:nth-child(2), -.dropzone-enabled table tr td:last-child { - vertical-align: top; -} -.dropzone-enabled table tr th:last-child, -.dropzone-enabled table tr td:last-child { - text-align: right; -} -.field--widget-file-generic td .form-managed-file .form-item { - padding: 10px 0 0 0; +.dropzone .dropzone-trigger.is-hovering { + background-position: -100px center; } -@media screen and (min-width: 414px) { - .field--widget-image-image td .image-widget-data .form-item { - padding-left: 27px; - } - .field--widget-file-generic td .form-managed-file .form-item { - padding: 10px 0 0 27px; - } +.dropzone .dropzone-trigger.is-hidden { + background-image: none; } -.dropzone-enabled table tr th:nth-child(2), -.dropzone-enabled table tr td:nth-child(2), -.dropzone-enabled table tr th:last-child, -.dropzone-enabled table tr td:last-child { - width: 30%; - white-space: nowrap; +/* Dropzone no-trigger area. */ +.dropzone .dropzone-no-trigger .js-dropzone-add-button { + margin: 20px 10px 5px 10px; } -.form-text { - width: 100%; -} -.field--type-image .details-wrapper, -.field--type-file .details-wrapper { - overflow: auto; +.dropzone .dropzone-no-trigger .js-dropzone-add-button:before { + margin-left: -0.2em; + content: "+"; + font-weight: 900; } -.dropzone-enabled table { - margin: 10px 0; - width: auto; +.dropzone .dropzone-no-trigger .description { + display: table-cell; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + max-width: 500px; + padding: 0 10px 20px 10px; } -@media screen and (min-width: 414px) { - .dropzone-enabled table tr th:last-child, - .dropzone-enabled table tr td:last-child, - .dropzone-enabled table tr th:nth-child(2), - .dropzone-enabled table tr td:nth-child(2) { - width: 30%; - white-space: nowrap; - } - .dropzone-enabled table { - table-layout: fixed; - } +.dropzone .dropzone-no-trigger .description li { + display: inline-block; } -@media screen and (min-width: 767px) { - .dropzone-enabled table tr th:last-child, - .dropzone-enabled table tr td:last-child, - .dropzone-enabled table tr th:nth-child(2), - .dropzone-enabled table tr td:nth-child(2) { - width: 25%; - white-space: nowrap; - } - .dropzone .image-widget .form-text { - width: auto; - } +.dropzone .dropzone-no-trigger .description li:after { + padding: 0 5px; + content: "\00b7"; } -.field--widget-image-image td .image-widget-data { - float: none; +.dropzone .dropzone-no-trigger .description li:last-child:after { + content: none; } -.field--widget-image-image td .image-preview img { - padding-bottom: 10px; +.dropzone .dropzone-no-trigger .image-preview { + display: none; } - -.field--widget-image-image td .image-widget-data .form-item { - margin-top: 1em; +.dropzone .dropzone-no-trigger .messages { + margin: 10px 10px 0 18px; } -.field--widget-file-generic td .form-managed-file .form-item { - padding: 0 0 0 27px; +/** + * Upload buttons AJAX throbber. + */ +.dropzone .upload-button + .ajax-progress { + position: absolute; + top: 50px; + left: -50px; + margin: -10px -15px; } - -[dir="rtl"] .field--widget-file-generic td .form-managed-file .form-item { - padding: 0 27px 0 0; +[dir="rtl"] .dropzone .upload-button + .ajax-progress { + right: -50px; + left: auto; } /** - * Dropzone. + * Single file/image widget after upload. */ -.dropzone-wrapper { - width: 100%; +.form-managed-file-wrapper.dropzone-enabled.has-file { border: 1px solid #bfbfbf; border-radius: 2px; background: #fcfcfa; - box-sizing: border-box; -} -.dropzone { - position: relative; - width: 100%; - height: 100%; - min-height: 100px; } -.dropzone.empty { - display: table; -} -.dropzone.empty > div { - display: table-cell; - vertical-align: top; - box-sizing: border-box; -} -span.ajax-new-content.managed-file { - display: block; -} -.dropzone .has-file, -.dropzone .form-managed-file { - padding: 10px; + +.form-managed-file-wrapper.dropzone-enabled.has-file .form-managed-file { + margin-left: 100px; + padding: 1em; } -.dropzone.empty .form-managed-file { - padding: 0; +[dir="rtl"] .form-managed-file-wrapper.dropzone-enabled.has-file .form-managed-file { + margin-right: 100px; + margin-left: 0; } - -/* Hide error message. */ -.dropzone .messages--error { display: none; } - -/* Form file. */ -.dropzone .js-form-file { +.form-managed-file-wrapper.dropzone-enabled.has-file .form-managed-file:before { position: absolute; - left: 0; top: 0; - cursor: pointer; - min-height: 100px; - height: 100%; + left: -100px; + display: block; width: 100px; - display: inline-block; - opacity: 0; -} - -[dir="rtl"] .dropzone .js-form-file { - right: 0; - left: auto; -} - -.dropzone .file { - background: none; - padding: 0; -} - -.dropzone .dropzone-trigger { - position: relative; - text-align: center; - min-height: 100px; height: 100%; - width: 100px; + min-height: 100px; + content: ""; border-right: 1px solid #bfbfbf; - background: url('../../../../misc/icons/bebebe/dropzone-new.svg') -200px center no-repeat; + background: url("../../../../misc/icons/bebebe/dropzone-new.svg") -200px center no-repeat; } - -[dir="rtl"] .dropzone .dropzone-trigger { +[dir="rtl"] .form-managed-file-wrapper.dropzone-enabled.has-file .form-managed-file:before { + right: -100px; + left: auto; border-right: none; border-left: 1px solid #bfbfbf; } -.dropzone.empty .dropzone-trigger, -#edit-settings-default-image .dropzone.empty .dropzone-trigger { - display: table-cell; - background-position: 0 center; -} - -.dropzone .dropzone-trigger.is-hovering { - background-position: -100px center; -} - -.dropzone .dropzone-trigger.is-complete, -.field--type-image .dropzone .dropzone-trigger.is-complete, -.field--type-file .dropzone .dropzone-trigger.is-complete { - background-position: -200px center; -} - -.dropzone.empty .dropzone-trigger.is-complete { - background-position: 0 center; -} - -.dropzone.empty .image-preview { - padding-top: 10px; - padding-left: 10px; -} - -@media screen and (min-width: 767px) { - .dropzone.empty .image-widget { - float: left; - } +.form-managed-file-wrapper.dropzone-enabled.has-file .image-widget.form-managed-file { + margin-right: 0; + margin-left: 0; + padding: 0; } - -.dropzone-description { - padding: 10px; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - display: table-cell; +[dir="rtl"] .form-managed-file-wrapper.dropzone-enabled.has-file .image-widget.form-managed-file { + margin-right: 0; } - -.dropzone-preview { - border: 1px solid #ccc; - border-radius: 2px; - background: #fdfdfc; - margin-bottom: 1em; padding: 0.5em 1em; +.form-managed-file-wrapper.dropzone-enabled.has-file .image-widget.form-managed-file:before { + content: none; } -.dropzone-buttons { +.form-managed-file-wrapper.dropzone-enabled.has-file .image-widget .image-preview { + flex: 0 0 100px; + width: 100px; padding: 0; - margin: 0 10px 10px 0; - list-style: none; -} - -[dir="rtl"] .dropzone-buttons { - margin: 0 0 10px 10px; + border-right: 1px solid #bfbfbf; } - -.dropzone-buttons li { - display: inline-block; - margin: 0 10px 0 0; +[dir="rtl"] .form-managed-file-wrapper.dropzone-enabled.has-file .image-widget .image-preview { + border-right: none; + border-left: 1px solid #bfbfbf; } -[dir="rtl"] .dropzone-buttons li { - margin: 0 0 0 10px; +.form-managed-file-wrapper.dropzone-enabled.has-file .image-widget .image-preview img { + margin: 10px; } -.dropzone .dropzone-trigger.is-hidden, -.field--type-image .dropzone .dropzone-trigger.is-hidden, -.field--type-file .dropzone .dropzone-trigger.is-hidden { - background-image: none; +.form-managed-file-wrapper.dropzone-enabled.has-file .image-widget .image-widget-data { + padding: 1em; } - diff --git a/core/themes/seven/css/components/managed-file.css b/core/themes/seven/css/components/managed-file.css new file mode 100644 index 0000000000..2e8dc05134 --- /dev/null +++ b/core/themes/seven/css/components/managed-file.css @@ -0,0 +1,148 @@ +/** + * Image widget. + */ +.form-type-managed-file .form-managed-file { + position: relative; + display: block; +} + +/** + * Image widget. + */ +.form-type-managed-file .image-widget { + position: relative; + display: flex; +} + +.form-type-managed-file .image-widget .image-preview { + padding: 0 1em 0 0; +} +[dir="rtl"] .form-type-managed-file .image-widget .image-preview { + padding: 0 0 0 1em; +} + +.form-type-managed-file .image-widget .image-preview img { + width: 80px; + max-width: 80px; + height: auto; + border: 0; +} + +.form-type-managed-file .description ul { + margin: 0; + padding: 0; + list-style: none; +} + +/** + * Multi uploads table. + */ +.form-type-managed-file table { + width: 100%; + margin: 10px 0; + table-layout: fixed; +} + +.form-type-managed-file table tr td:first-child { + display: flex; +} + +.form-type-managed-file table tr.draggable td:first-child a.tabledrag-handle { + flex: 0 0 40px; +} + +.form-type-managed-file table tr th:nth-child(2), +.form-type-managed-file table tr td:nth-child(2) { + width: 40px; + vertical-align: top; + white-space: nowrap; +} + +.form-type-managed-file table tr th:last-child, +.form-type-managed-file table tr td:last-child { + position: relative; + width: 20px; + text-align: right; + vertical-align: top; + white-space: nowrap; +} + +.form-type-managed-file table tr th:last-child { + font-size: 0; +} + +/** + * Remove button. + */ +.form-type-managed-file .remove-button { + position: absolute; + top: 10px; + right: 10px; + width: 17px; + margin: 0; + padding: 0; + text-transform: lowercase; + border: 0; + background: url(../../../../misc/icons/787878/ex.svg) right 0 no-repeat; + font-size: 0; + line-height: 17px; +} +[dir="rtl"] .form-type-managed-file .remove-button { + right: auto; + left: 10px; + background-position: left 0; +} + +.form-type-managed-file .remove-button:focus { + outline: none; + box-shadow: none; +} + +.form-type-managed-file .remove-button:hover { + color: #e00; + border: 0; + background: url(../../../../misc/icons/ee0000/ex.svg) right 0 no-repeat; + box-shadow: none; +} +[dir="rtl"] .form-type-managed-file .remove-button:hover { + background-position: left 0; +} + +/** + * Remove buttons ajax throbber. + */ +.form-type-managed-file .remove-button + .ajax-progress { + position: absolute; + top: 8px; + right: 25px; +} +[dir="rtl"] .form-type-managed-file .remove-button + .ajax-progress { + right: auto; + left: 25px; +} + +/** + * Stop input elements from breaking out of their container. + */ +.form-type-managed-file .form-item { + margin-top: 0; +} + +/** + * Stop input elements from breaking out of their container. + */ +.form-type-managed-file input.form-text { + width: 100%; + max-width: 500px; +} + +/** + * Default image field. + */ +#edit-settings-default-image .form-managed-file-wrapper.dropzone-enabled.has-file { + min-height: 100px; +} +#edit-settings-default-image .form-managed-file-wrapper.dropzone-enabled.has-file .description { + margin-left: 100px; + padding: 0 1em 1em; +} diff --git a/core/themes/seven/js/dropzone.es6.js b/core/themes/seven/js/dropzone.es6.js new file mode 100644 index 0000000000..1fa975aaa7 --- /dev/null +++ b/core/themes/seven/js/dropzone.es6.js @@ -0,0 +1,192 @@ +(function($, Drupal, drupalSettings) { + /** + * Initializes a dropzone for managed file fields. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches a dropzone to managed file fields. + */ + Drupal.behaviors.dropzone = { + attach(context) { + // Add 'dropzone-enabled' class to managed file fields. + $(context) + .find('.form-managed-file-wrapper') + .addClass('dropzone-enabled'); + + // Add dropzone HTML to managed file fields. + $(context) + .find('.js-form-file') + .once('managed-file-dropzone') + .each(function() { + const $wrapper = $(this).parents('.form-managed-file-wrapper'); + + // Create extra button and add to wrapper. + let buttonText = 'Add file'; + if (this.hasAttribute('multiple')) { + buttonText = 'Add files'; + } + const $button = $( + `` + ); + $button.insertAfter($wrapper.find('.form-managed-file')); + + // Create dropzone HTML. + const $dropzone = $(`
+
+
+
+
`); + + // Insert dropzone before the wrapper and move the wrapper to the + // dropzone. + $dropzone + .insertBefore($wrapper) + .find('.dropzone-no-trigger') + .append($wrapper); + + // Attach listeners for drag/drop events. + $(this) + .on('dragover mouseenter', e => { + e.preventDefault(); + e.stopPropagation(); + $(e.currentTarget) + .closest('.dropzone') + .find('.dropzone-trigger') + .addClass('is-hovering'); + }) + .on('dragleave mouseleave', e => { + e.preventDefault(); + $(e.currentTarget) + .closest('.dropzone') + .find('.dropzone-trigger') + .removeClass('is-hovering'); + }); + }); + + Drupal.ajax.instances + .filter(instance => { + if (instance && instance.element) { + const element = $(instance.element); + return ( + element.length > 0 && + element.hasClass('upload-button') && + element.once('magic').length > 0 + ); + } + return null; + }) + .forEach(instance => { + const $element = $(instance.element); + $element + .closest('.dropzone') + .find('.js-dropzone-add-button') + .on('click', e => { + e.preventDefault(); + $(e.currentTarget) + .closest('.js-form-item') + .find('input[type="file"]') + .trigger('click'); + }); + + const beforeSend = instance.beforeSend; + $element.bind('beforeSend', beforeSend); + + instance.beforeSend = function(xmlhttprequest, options) { + beforeSend.call(this, xmlhttprequest, options); + $element.trigger('beforeSend'); + $element + .closest('.dropzone') + .find('.dropzone-trigger') + .addClass('is-hidden'); + }; + + instance.success = function(response, status) { + $(this.$element).prop('disabled', false); + + // Save element's ancestors tree so if the element is removed from + // the dom we can try to refocus one of its parents. Using addBack + // reverse the result array, meaning that index 0 is the highest + // parent in the hierarchy in this situation it is usually a
+ // element. + const $elementParents = $(this.element) + .parents('[data-drupal-selector]') + .addBack() + .toArray(); + + // Track if any command is altering the focus so we can avoid + // changing the focus set by the Ajax command. + let focusChanged = false; + for (const i in response) { + if ( + response.hasOwnProperty(i) && + response[i].command && + this.commands[response[i].command] + ) { + this.commands[response[i].command](this, response[i], status); + if ( + response[i].command === 'invoke' && + response[i].method === 'focus' + ) { + focusChanged = true; + } + } + } + + // If the focus hasn't be changed by the ajax commands, try to + // refocus the triggering element or one of its parents if that + // element does not exist anymore. + let target; + if ( + !focusChanged && + this.element && + !$(this.element).data('disable-refocus') + ) { + target = false; + for (let n = $elementParents.length - 1; !target && n > 0; n--) { + const selector = $elementParents[n].getAttribute( + 'data-drupal-selector', + ); + target = document.querySelector( + `[data-drupal-selector="${selector}"]`, + ); + } + if (target) { + $(target).trigger('focus'); + } + } + + // Reattach behaviors, if they were detached in beforeSerialize(). + // The attachBehaviors() called on the new content from processing + // the response commands is not sufficient, because behaviors from + // the entire form need to be reattached. + if (this.$form) { + const settings = this.settings || drupalSettings; + Drupal.attachBehaviors(this.$form.get(0), settings); + } + + // Remove any response-specific settings so they don't get used on + // the next call by mistake. + this.settings = null; + }; + }); + + /** + * Fix the reflowing of the changed marker. Output after filesize span. + */ + if (Drupal.tableDrag) { + Drupal.tableDrag.prototype.row.prototype.markChanged = function() { + const marker = Drupal.theme('tableDragChangedMarker'); + const cell = $(this.element).find('td:first-of-type'); + if (cell.find('abbr.tabledrag-changed').length === 0) { + if (cell.find('.file-size').length) { + cell.find('.file-size').append(marker); + } else { + cell.append(marker); + } + } + }; + } + }, + }; +})(jQuery, Drupal, drupalSettings); diff --git a/core/themes/seven/js/dropzone.js b/core/themes/seven/js/dropzone.js index 66871a5c7e..e406536417 100644 --- a/core/themes/seven/js/dropzone.js +++ b/core/themes/seven/js/dropzone.js @@ -1,28 +1,97 @@ -(function ($, Drupal) { +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function ($, Drupal, drupalSettings) { + Drupal.behaviors.dropzone = { + attach: function attach(context) { + $(context).find('.form-managed-file-wrapper').addClass('dropzone-enabled'); + + $(context).find('.js-form-file').once('managed-file-dropzone').each(function () { + var $wrapper = $(this).parents('.form-managed-file-wrapper'); + + var buttonText = 'Add file'; + if (this.hasAttribute('multiple')) { + buttonText = 'Add files'; + } + var $button = $(''); + $button.insertAfter($wrapper.find('.form-managed-file')); + + var $dropzone = $('
\n
\n
\n
\n
'); + + $dropzone.insertBefore($wrapper).find('.dropzone-no-trigger').append($wrapper); + + $(this).on('dragover mouseenter', function (e) { + e.preventDefault(); + e.stopPropagation(); + $(e.currentTarget).closest('.dropzone').find('.dropzone-trigger').addClass('is-hovering'); + }).on('dragleave mouseleave', function (e) { + e.preventDefault(); + $(e.currentTarget).closest('.dropzone').find('.dropzone-trigger').removeClass('is-hovering'); + }); + }); - 'use strict'; + Drupal.ajax.instances.filter(function (instance) { + if (instance && instance.element) { + var element = $(instance.element); + return element.length > 0 && element.hasClass('upload-button') && element.once('magic').length > 0; + } + return null; + }).forEach(function (instance) { + var $element = $(instance.element); + $element.closest('.dropzone').find('.js-dropzone-add-button').on('click', function (e) { + e.preventDefault(); + $(e.currentTarget).closest('.js-form-item').find('input[type="file"]').trigger('click'); + }); - Drupal.behaviors.dropzone = { - attach: function (context) { + var beforeSend = instance.beforeSend; + $element.bind('beforeSend', beforeSend); - var formFile = $(context).find('.js-form-file'); - var detailsWrapper = $(context).find('.dropzone').parents('details'); + instance.beforeSend = function (xmlhttprequest, options) { + beforeSend.call(this, xmlhttprequest, options); + $element.trigger('beforeSend'); + $element.closest('.dropzone').find('.dropzone-trigger').addClass('is-hidden'); + }; - formFile.on('dragover mouseenter', function () { - $(this).closest('.dropzone').find('.dropzone-trigger').addClass('is-hovering'); - }); - formFile.on('dragleave mouseleave', function () { - $(this).closest('.dropzone').find('.dropzone-trigger').removeClass('is-hovering'); - }); + instance.success = function (response, status) { + $(this.$element).prop('disabled', false); - // Add a class to details wrapper so we can style the table with more control - if (detailsWrapper.length) { - $('.dropzone').parents('details').addClass('dropzone-enabled'); - } + var $elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray(); + + var focusChanged = false; + for (var i in response) { + if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { + this.commands[response[i].command](this, response[i], status); + if (response[i].command === 'invoke' && response[i].method === 'focus') { + focusChanged = true; + } + } + } + + var target = void 0; + if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) { + target = false; + for (var n = $elementParents.length - 1; !target && n > 0; n--) { + var selector = $elementParents[n].getAttribute('data-drupal-selector'); + target = document.querySelector('[data-drupal-selector="' + selector + '"]'); + } + if (target) { + $(target).trigger('focus'); + } + } + + if (this.$form) { + var settings = this.settings || drupalSettings; + Drupal.attachBehaviors(this.$form.get(0), settings); + } + + this.settings = null; + }; + }); - /** - * Fix the reflowing of the changed marker. Output after filesize span. - */ if (Drupal.tableDrag) { Drupal.tableDrag.prototype.row.prototype.markChanged = function () { var marker = Drupal.theme('tableDragChangedMarker'); @@ -30,95 +99,12 @@ if (cell.find('abbr.tabledrag-changed').length === 0) { if (cell.find('.file-size').length) { cell.find('.file-size').append(marker); - } - else { + } else { cell.append(marker); } } }; } - - - Drupal.ajax.instances - .filter(function (instance) { - if (instance && instance.element) { - var element = $(instance.element); - return element.length > 0 && element.hasClass('upload-button') && element.once('magic').length > 0; - } - else { - return null; - } - }) - .forEach(function (instance) { - var element = $(instance.element); - element.closest('.dropzone').find('.js-upload-image').on('click', function () { - $(this).closest('.js-form-item').find('input[type="file"]').trigger('click'); - }); - - var beforeSend = instance.beforeSend; - element.bind('beforeSend', beforeSend); - - instance.beforeSend = function (xmlhttprequest, options) { - beforeSend.call(this, xmlhttprequest, options); - element.trigger('beforeSend'); - element.closest('.dropzone').find('.dropzone-trigger').addClass('is-hidden'); - }; - - - instance.success = function (response, status) { - $(this.element).prop('disabled', false); - - // Save element's ancestors tree so if the element is removed from the dom - // we can try to refocus one of its parents. Using addBack reverse the - // result array, meaning that index 0 is the highest parent in the hierarchy - // in this situation it is usually a element. - var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray(); - - // Track if any command is altering the focus so we can avoid changing the - // focus set by the Ajax command. - var focusChanged = false; - for (var i in response) { - if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { - this.commands[response[i].command](this, response[i], status); - if (response[i].command === 'invoke' && response[i].method === 'focus') { - focusChanged = true; - } - } - } - - // If the focus hasn't be changed by the ajax commands, try to refocus the - // triggering element or one of its parents if that element does not exist - // anymore. - var target; - if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) { - target = false; - for (var n = elementParents.length - 1; !target && n > 0; n--) { - target = document.querySelector('[data-drupal-selector="' + elementParents[n].getAttribute('data-drupal-selector') + '"]'); - } - if (target) { - $(target).trigger('focus'); - } - } - - if (target && $(this.element).hasClass('upload-button')) { - $(target).find('.dropzone-trigger').addClass('is-complete').next('.js-form-managed-file').addClass('has-file'); - } - - // Reattach behaviors, if they were detached in beforeSerialize(). The - // attachBehaviors() called on the new content from processing the response - // commands is not sufficient, because behaviors from the entire form need - // to be reattached. - if (this.$form) { - var settings = this.settings || drupalSettings; - Drupal.attachBehaviors(this.$form.get(0), settings); - } - - // Remove any response-specific settings so they don't get used on the next - // call by mistake. - this.settings = null; - }; - }); } }; - -})(jQuery, Drupal); +})(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml index 21ebbfee5a..912d190ff8 100644 --- a/core/themes/seven/seven.libraries.yml +++ b/core/themes/seven/seven.libraries.yml @@ -20,6 +20,7 @@ global-styling: css/components/field-ui.css: {} css/components/form.css: {} css/components/help.css: {} + css/components/managed-file.css: {} css/components/menus-and-lists.css: {} css/components/modules-page.css: {} css/components/node.css: {} diff --git a/core/themes/seven/templates/file-upload-help.html.twig b/core/themes/seven/templates/file-upload-help.html.twig new file mode 100644 index 0000000000..2e46af2fe3 --- /dev/null +++ b/core/themes/seven/templates/file-upload-help.html.twig @@ -0,0 +1,16 @@ +{# +/** + * @file + * Theme override to display help text for file fields. + * + * Available variables: + * - descriptions: Lines of help text for uploading a file. + * + * @see template_preprocess_file_upload_help() + */ +#} + diff --git a/core/themes/seven/templates/file-widget-multiple.html.twig b/core/themes/seven/templates/file-widget-multiple.html.twig new file mode 100644 index 0000000000..8504310e9a --- /dev/null +++ b/core/themes/seven/templates/file-widget-multiple.html.twig @@ -0,0 +1,16 @@ +{# +/** + * @file + * Theme override to display a multi file form widget. + * + * Available variables: + * - table: Table of previously uploaded files. + * - element: The form element for uploading another file. + * + * @see template_preprocess_file_widget_multiple() + */ +#} +
+ {{ table }} + {{ element }} +
\ No newline at end of file diff --git a/core/themes/seven/templates/form-element--managed-file.html.twig b/core/themes/seven/templates/form-element--managed-file.html.twig index 4250f73c25..673e46148f 100644 --- a/core/themes/seven/templates/form-element--managed-file.html.twig +++ b/core/themes/seven/templates/form-element--managed-file.html.twig @@ -1,7 +1,7 @@ {# /** * @file - * Theme override for a managed file form element. + * Theme override for a form element. * * Available variables: * - attributes: HTML attributes for the containing element. @@ -45,61 +45,53 @@ */ #} {% -set classes = [ -'js-form-item', -'form-item', -'js-form-type-' ~ type|clean_class, -'form-type-' ~ type|clean_class, -'js-form-item-' ~ name|clean_class, -'form-item-' ~ name|clean_class, -'dropzone-wrapper', -'clearfix', -disabled == 'disabled' ? 'form-disabled', -errors ? 'form-item--error', + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-type-' ~ type|clean_class, + 'js-form-item-' ~ name|clean_class, + 'form-item-' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-no-label', + disabled == 'disabled' ? 'form-disabled', + errors ? 'form-item--error', ] %} {% -set description_classes = [ -'dropzone-description', -description_display == 'invisible' ? 'visually-hidden', + set description_classes = [ + 'description', + description_display == 'invisible' ? 'visually-hidden', ] %} - + {% if label_display in ['before', 'invisible'] %} + {{ label }} + {% endif %} {% if prefix is not empty %} {{ prefix }} {% endif %} -
-
-
- {{ children }} - - {% if suffix is not empty %} - {{ suffix }} - {% endif %} - {% if label_display == 'after' %} - {{ label }} - {% endif %} - - {% if description.content %} - -
    -
  • - - {% set count = element['#multiple'] ? 1 : 2 %} - {% trans %} - Add Files - {% plural count %} - Add File - {% endtrans %} - -
  • - {#
  • {{ 'Browse Library'|t }}
  • #} -
- {{ description.content }} -
- {% endif %} -
+ {% if description_display == 'before' and description.content %} + + {{ description.content }} + + {% endif %} +
+ {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {{ children }} + {% if description_display in ['after', 'invisible'] and description.content %} + + {{ description.content }} +
+ {% endif %} - + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label_display == 'after' %} + {{ label }} + {% endif %}