diff --git a/EMS/admin-ui-bundle/_to_be_ref/js/editRevisionEventListeners.js b/EMS/admin-ui-bundle/_to_be_ref/js/editRevisionEventListeners.js index 841c0e5f2..274111039 100644 --- a/EMS/admin-ui-bundle/_to_be_ref/js/editRevisionEventListeners.js +++ b/EMS/admin-ui-bundle/_to_be_ref/js/editRevisionEventListeners.js @@ -223,18 +223,6 @@ function editRevisionEventListeners(target, onChangeCallback = null){ } }); - target.find('.datetime-picker').each(function( ) { - let $element = $(this); - $element.unbind('change'); - $element.datetimepicker({ - keepInvalid: true, //otherwise daysOfWeekDisabled or disabledHours will not work! - extraFormats: [moment.ISO_8601] - }); - if (onChangeCallback) { - $element.not(".ignore-ems-update").on('dp.change', onChangeCallback); - } - }); - target.find('.ems_daterangepicker').each(function( ) { const options = $(this).data('display-option'); diff --git a/EMS/admin-ui-bundle/_to_be_ref/js/initEms.js b/EMS/admin-ui-bundle/_to_be_ref/js/initEms.js index 5fad2e15b..029f21f19 100644 --- a/EMS/admin-ui-bundle/_to_be_ref/js/initEms.js +++ b/EMS/admin-ui-bundle/_to_be_ref/js/initEms.js @@ -38,12 +38,6 @@ import ajaxModal from "../../js/core/helpers/ajaxModal"; }); } - function autoOpenModal(queryString) { - if(queryString.open) { - $('#content_type_structure_fieldType'+queryString.open).modal('show'); - } - } - function initSearchForm() { $('#add-search-filter-button').on('click', function(e) { @@ -88,7 +82,6 @@ import ajaxModal from "../../js/core/helpers/ajaxModal"; closeModalNotification(); toggleMenu(); initSearchForm(); - autoOpenModal(queryString()); intAjaxModalLinks(); }); diff --git a/EMS/admin-ui-bundle/assets/js/core/components/jsonMenu.js b/EMS/admin-ui-bundle/assets/js/core/components/jsonMenu.js index b2ab6f16b..f6efc6b0a 100644 --- a/EMS/admin-ui-bundle/assets/js/core/components/jsonMenu.js +++ b/EMS/admin-ui-bundle/assets/js/core/components/jsonMenu.js @@ -1,8 +1,8 @@ import $ from 'jquery' import { ChangeEvent } from '../events/changeEvent' -require('../librairies/nestedSortable') +import { v4 } from 'uuid' -const uuidv4 = require('uuid/v4') +require('../librairies/nestedSortable') export default class JsonMenu { constructor (target) { @@ -63,7 +63,7 @@ export default class JsonMenu { } addItem ($target, prototypeTarget, data) { - const uuid = uuidv4() + const uuid = v4() let itemHtml = this.parent.find('.json_menu_editor_fieldtype_widget').data(prototypeTarget) itemHtml = itemHtml.replace(/%uuid%/g, uuid) for (const [key, value] of Object.entries(data)) { diff --git a/EMS/admin-ui-bundle/assets/js/core/components/jsonMenuNested.js b/EMS/admin-ui-bundle/assets/js/core/components/jsonMenuNested.js index e75276e06..49ef12907 100644 --- a/EMS/admin-ui-bundle/assets/js/core/components/jsonMenuNested.js +++ b/EMS/admin-ui-bundle/assets/js/core/components/jsonMenuNested.js @@ -2,11 +2,10 @@ import ajaxModal from '../../../js/core/helpers/ajaxModal' import { ajaxJsonPost } from '../helpers/ajax' import collapse from '../helpers/collapse' import $ from 'jquery' +import { v4 } from 'uuid' require('../librairies/nestedSortable') -const uuidv4 = require('uuid/v4') - export default class JsonMenuNested { copyName = 'json_menu_nested_copy' nodes = {} @@ -198,7 +197,7 @@ export default class JsonMenuNested { const json = JSON.parse(localStorage.getItem(this.copyName)) - return loopJson(json, (key, value) => key === 'id' && value !== '_root' ? uuidv4() : value) + return loopJson(json, (key, value) => key === 'id' && value !== '_root' ? v4() : value) } return false @@ -223,7 +222,7 @@ export default class JsonMenuNested { } const node = this.nodes[nodeId] - const addItemId = uuidv4() + const addItemId = v4() const params = new URLSearchParams(window.location.search) @@ -345,7 +344,7 @@ export default class JsonMenuNested { } return { - id: uuidv4(), + id: v4(), label: item.label, type: item.type, object: item.object, diff --git a/EMS/admin-ui-bundle/assets/js/core/components/modal.js b/EMS/admin-ui-bundle/assets/js/core/components/modal.js new file mode 100644 index 000000000..e8ccecedf --- /dev/null +++ b/EMS/admin-ui-bundle/assets/js/core/components/modal.js @@ -0,0 +1,18 @@ +'use strict' +import queryString from '../helpers/queryString' +import { Modal as BSModal } from 'bootstrap' + +export default class Modal { + constructor () { + this.autoOpenModal() + } + + autoOpenModal () { + const queryStringObject = queryString() + if (queryStringObject.open) { + const modalElement = document.getElementById(`content_type_structure_fieldType${queryStringObject.open}`) + const modal = BSModal.getOrCreateInstance(modalElement) + modal.show() + } + } +} diff --git a/EMS/admin-ui-bundle/assets/js/core/core.js b/EMS/admin-ui-bundle/assets/js/core/core.js index 2666ccc3c..6f56d5edd 100644 --- a/EMS/admin-ui-bundle/assets/js/core/core.js +++ b/EMS/admin-ui-bundle/assets/js/core/core.js @@ -3,6 +3,7 @@ import Choice from './plugins/choice' import CodeEditor from './plugins/codeEditor' import CollapsibleCollection from './plugins/collapsibleCollection' import Datatable from './plugins/datatable' +import Datetime from './plugins/datetime' import File from './plugins/file' import Form from './plugins/form' import Iframe from './plugins/iframe' @@ -21,6 +22,7 @@ import Tooltip from './plugins/tooltip' import WYSIWYG from './plugins/wysiwyg' import RevisionTask from './components/revisionTask' +import Modal from './components/modal' import Sidebar from './components/sidebar' import { EMS_ADDED_DOM_EVENT } from './events/addedDomEvent' @@ -36,6 +38,7 @@ class Core { new CodeEditor(), new CollapsibleCollection(), new Datatable(), + new Datetime(), new File(), new Form(), new Iframe(), @@ -75,6 +78,7 @@ class Core { this.initCtrlSaveEvent() this.components = [ new RevisionTask(), + new Modal(), new Sidebar() ] } diff --git a/EMS/admin-ui-bundle/assets/js/core/librairies/nestedSortable.js b/EMS/admin-ui-bundle/assets/js/core/librairies/nestedSortable.js index 785dc9a0a..75612dfa6 100644 --- a/EMS/admin-ui-bundle/assets/js/core/librairies/nestedSortable.js +++ b/EMS/admin-ui-bundle/assets/js/core/librairies/nestedSortable.js @@ -11,6 +11,9 @@ * http://www.opensource.org/licenses/mit-license.php */ /* eslint-disable */ +require('jquery-ui'); +require('jquery-ui/ui/widgets/sortable'); + (function (factory) { 'use strict' @@ -136,6 +139,12 @@ this.lastPositionAbs = this.positionAbs } + + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + // Do scrolling if (this.options.scroll) { if (false && this.scrollParent[0] !== document && this.scrollParent[0].tagName !== 'HTML') { diff --git a/EMS/admin-ui-bundle/assets/js/core/plugins/datetime.js b/EMS/admin-ui-bundle/assets/js/core/plugins/datetime.js new file mode 100644 index 000000000..bcee14682 --- /dev/null +++ b/EMS/admin-ui-bundle/assets/js/core/plugins/datetime.js @@ -0,0 +1,42 @@ +import { TempusDominus } from '@eonasdan/tempus-dominus' +import '@eonasdan/tempus-dominus/src/scss/tempus-dominus.scss' +import ChangeEvent from '../events/changeEvent' + +class Datetime { + #iframes = [] + + load (target) { + const datetimePickers = target.querySelectorAll('.datetime-picker') + for (let i = 0; i < datetimePickers.length; i++) { + const picker = new TempusDominus(datetimePickers[i], { + display: { + buttons: { + today: true, + clear: true, + close: true + } + }, + localization: { + format: datetimePickers[i].dataset.dateFormat, + startOfTheWeek: 1 + }, + restrictions: { + daysOfWeekDisabled: JSON.parse(datetimePickers[i].dataset.dateDaysOfWeekDisabled), + disabledHours: JSON.parse(datetimePickers[i].dataset.dateDisabledHours) + } + }) + if (datetimePickers[i].dataset.dateLocale) { + picker.locale(datetimePickers[i].dataset.dateLocale) + } + datetimePickers[i].addEventListener('change.td', function () { + if (datetimePickers[i].classList.contains('ignore-ems-update')) { + return + } + const event = new ChangeEvent(datetimePickers[i]) + event.dispatch() + }) + } + } +} + +export default Datetime diff --git a/EMS/admin-ui-bundle/assets/js/core/plugins/text.js b/EMS/admin-ui-bundle/assets/js/core/plugins/text.js index ae7361573..593e24f05 100644 --- a/EMS/admin-ui-bundle/assets/js/core/plugins/text.js +++ b/EMS/admin-ui-bundle/assets/js/core/plugins/text.js @@ -7,8 +7,8 @@ class Text { const spans = target.querySelectorAll('.text-counter[data-counter-label]') for (let i = 0; i < spans.length; ++i) { const span = spans[i] - const input = span.parentNode.querySelector('textarea,input') - if (!input || input.parentNode !== span.parentNode) { + const input = span.parentElement.querySelector('textarea,input') + if (input === null) { return } const counterLabel = span.dataset.counterLabel diff --git a/EMS/admin-ui-bundle/assets/js/edit-revision.js b/EMS/admin-ui-bundle/assets/js/edit-revision.js index 431a8a3ed..9135739ac 100644 --- a/EMS/admin-ui-bundle/assets/js/edit-revision.js +++ b/EMS/admin-ui-bundle/assets/js/edit-revision.js @@ -122,38 +122,86 @@ function onChange (allowAutoPublish = false) { waitingResponse = ajaxRequest.post(primaryBox.data('ajax-update'), $('form[name=revision]').serialize()) .success(function (response) { $('.is-invalid').removeClass('is-invalid') - $('span.help-block').remove() + $('.has-error').removeClass('has-error') + $('.invalid-feedback').html('') $(response.formErrors).each(function (index, item) { let target = item.propertyPath - const targetLabel = $('#' + target + '__label') - const targetError = $('#' + target + '__error') - - const propPath = $('#' + item.propertyPath + '_value') - if (propPath.length && propPath.prop('nodeName') === 'TEXTAREA') { - target = item.propertyPath + '_value' + let targetElement = document.getElementById(target) + if (targetElement === null) { + targetElement = document.getElementById(`${target}_value`) + if (targetElement !== null) { + target = `${target}_value` + } } - - const targetParent = $('#' + target) - if (targetLabel.length) { - targetLabel.closest('div.form-group').addClass('has-error') - if (item.message && targetError.length > 0) { - targetError.addClass('has-error') - if ($('#' + target + '__error span.help-block').length === 0) { - targetError.append('') + if (targetElement !== null) { + switch (targetElement.nodeName) { + case 'DIV': { + const previousElement = targetElement.previousElementSibling + targetElement.classList.add('has-error') + if (previousElement !== null && previousElement.classList.contains('invalid-feedback') && item.message) { + $(previousElement).html(item.message) + } else { + console.log(targetElement) + } + break } - $('#' + target + '__error' + ' span.help-block ul.list-unstyled').append('
  • ' + item.message + '
  • ') - } - } else { - $('#' + target).closest('div.form-group').addClass('has-error') - targetParent.parents('.form-group').addClass('has-error') - if (item.message) { - if (targetParent.parents('.form-group').find(' span.help-block').length === 0) { - targetParent.parent('.form-group').append('') - } else { - targetParent.parents('.form-group').find(' span.help-block ul.list-unstyled').append('
  • ' + item.message + '
  • ') + case 'INPUT': { + targetElement.classList.add('is-invalid') + const label = document.querySelector(`label[for=${target}]`) + if (label !== null) { + let parent = label.parentElement + if (parent.classList.contains('input-group')) { + parent = parent.parentElement + } + const invalidFeedback = label.parentElement.querySelector('.invalid-feedback') + if (invalidFeedback !== null) { + invalidFeedback.textContent = item.message + } else { + console.log(targetElement) + } + } else { + console.log(targetElement) + } + break + } + default: { + console.log(targetElement) + console.log(item) } } + } else { + console.log(item) } + + // const targetLabel = $('#' + target + '__label') + // const targetError = $('#' + target + '__error') + // + // const propPath = $('#' + item.propertyPath + '_value') + // if (propPath.length && propPath.prop('nodeName') === 'TEXTAREA') { + // target = item.propertyPath + '_value' + // } + + // const targetParent = $('#' + target) + // if (targetLabel.length) { + // targetLabel.closest('div.form-group').addClass('has-error') + // if (item.message && targetError.length > 0) { + // targetError.addClass('has-error') + // if ($('#' + target + '__error span.help-block').length === 0) { + // targetError.append('') + // } + // $('#' + target + '__error' + ' span.help-block ul.list-unstyled').append('
  • ' + item.message + '
  • ') + // } + // } else { + // $('#' + target).closest('div.form-group').addClass('has-error') + // targetParent.parents('.form-group').addClass('has-error') + // if (item.message) { + // if (targetParent.parents('.form-group').find(' span.help-block').length === 0) { + // targetParent.parent('.form-group').append('') + // } else { + // targetParent.parents('.form-group').find(' span.help-block ul.list-unstyled').append('
  • ' + item.message + '
  • ') + // } + // } + // } }) }) .always(function () { diff --git a/EMS/admin-ui-bundle/package-lock.json b/EMS/admin-ui-bundle/package-lock.json index 1bfe6a9ca..939d95938 100644 --- a/EMS/admin-ui-bundle/package-lock.json +++ b/EMS/admin-ui-bundle/package-lock.json @@ -11,6 +11,7 @@ "@ckeditor/ckeditor5-dev-utils": "^39.5.1", "@ckeditor/ckeditor5-theme-lark": "^40.2.0", "@elasticms/file-uploader": "^1.0.1", + "@eonasdan/tempus-dominus": "^6.9.5", "@fortawesome/fontawesome-free": "^6.4.0", "@popperjs/core": "^2.11.8", "ace-builds": "^1.32.3", @@ -23,7 +24,8 @@ "jquery-lazyload": "^1.9.7", "jquery-ui": "^1.13.2", "select2": "^4.1.0-rc.0", - "sortablejs": "^1.15.1" + "sortablejs": "^1.15.1", + "uuid": "^9.0.1" }, "devDependencies": { "clean-webpack-plugin": "^4.0.0", @@ -1137,6 +1139,22 @@ "crypto-js": "^4.1.1" } }, + "node_modules/@eonasdan/tempus-dominus": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@eonasdan/tempus-dominus/-/tempus-dominus-6.9.5.tgz", + "integrity": "sha512-ZH768JZx6K1iw8UpZq9AG1VK2gK4G8wApfwi+nhnfuIpXqIjZit4fgoCWQhofEDORxC8dlg4OCjuvPhn7TB9vg==", + "funding": { + "url": "https://ko-fi.com/eonasdan" + }, + "peerDependencies": { + "@popperjs/core": "^2.11.6" + }, + "peerDependenciesMeta": { + "@popperjs/core\"": { + "optional": true + } + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -8336,6 +8354,18 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vanilla-colorful": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", @@ -9637,6 +9667,12 @@ "crypto-js": "^4.1.1" } }, + "@eonasdan/tempus-dominus": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@eonasdan/tempus-dominus/-/tempus-dominus-6.9.5.tgz", + "integrity": "sha512-ZH768JZx6K1iw8UpZq9AG1VK2gK4G8wApfwi+nhnfuIpXqIjZit4fgoCWQhofEDORxC8dlg4OCjuvPhn7TB9vg==", + "requires": {} + }, "@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -14559,6 +14595,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, "vanilla-colorful": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", diff --git a/EMS/admin-ui-bundle/package.json b/EMS/admin-ui-bundle/package.json index 77771541e..a50d64ea6 100644 --- a/EMS/admin-ui-bundle/package.json +++ b/EMS/admin-ui-bundle/package.json @@ -33,6 +33,7 @@ "@ckeditor/ckeditor5-dev-utils": "^39.5.1", "@ckeditor/ckeditor5-theme-lark": "^40.2.0", "@elasticms/file-uploader": "^1.0.1", + "@eonasdan/tempus-dominus": "^6.9.5", "@fortawesome/fontawesome-free": "^6.4.0", "@popperjs/core": "^2.11.8", "ace-builds": "^1.32.3", @@ -45,6 +46,7 @@ "jquery-lazyload": "^1.9.7", "jquery-ui": "^1.13.2", "select2": "^4.1.0-rc.0", - "sortablejs": "^1.15.1" + "sortablejs": "^1.15.1", + "uuid": "^9.0.1" } } diff --git a/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig b/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig index ead418d28..628ca16c5 100644 --- a/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig +++ b/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig @@ -106,13 +106,16 @@ {% block icontext_widget -%} {% if prefixIcon or prefixText or suffixIcon or suffixText %} + {% if not valid or errors|length > 0 %} + {% set attr = attr|merge({class: attr.class|default('') ~ ' is-invalid'}) %} + {% endif %}
    {% if prefixIcon or prefixText %} - {% if prefixText %}   {{ prefixText }} {% endif %} + {% if prefixText %}   {{ prefixText }} {% endif %} {% endif %} {{ block('form_widget') }} {% if suffixIcon or suffixText %} - {% if suffixText %}   {{ suffixText }} {% endif %} + {% if suffixText %}   {{ suffixText }} {% endif %} {% endif %}
    {% else %} @@ -960,19 +963,19 @@ {% endblock datefieldtype_row %} {% block date_time_field_type_row %} -
    -
    - {{ form_label(form.value) }} -
    +
    + {{ form_label(form.value) }}
    -
    - -
    - {{- form_widget(form.value) -}} -
    -
    - {{- form_errors(form) -}} + + + + {% if errors|length > 0 or not valid %} + {{- form_widget(form.value, { 'attr': {'class': form.value.vars.attr.class|default('') ~ ' is-invalid'} }) -}} + {% else %} + {{- form_widget(form.value) -}} + {% endif %}
    + {{- form_errors(form) -}}
    {% endblock date_time_field_type_row %} diff --git a/EMS/core-bundle/src/Core/Form/FieldTypeManager.php b/EMS/core-bundle/src/Core/Form/FieldTypeManager.php index 7e425d39c..52dbcedad 100644 --- a/EMS/core-bundle/src/Core/Form/FieldTypeManager.php +++ b/EMS/core-bundle/src/Core/Form/FieldTypeManager.php @@ -241,17 +241,25 @@ private function reorderFields(array $formArray, FieldType $fieldType): bool if (\array_key_exists('reorder', $formArray)) { /** @var string[] $keys */ $keys = \array_keys($formArray); + $fields = []; foreach ($fieldType->getChildren() as $child) { - if (!$child instanceof FieldType) { - throw new \RuntimeException('Unexpected FieldType object'); + $fields[] = $child; + } + \usort($fields, function (FieldType $a, FieldType $b) use ($keys) { + $orderA = \array_search('ems_'.$a->getName(), $keys, true); + $orderB = \array_search('ems_'.$b->getName(), $keys, true); + if (!\is_int($orderA)) { + $orderA = $a->getOrderKey(); } - if (!$child->getDeleted()) { - $order = \array_search('ems_'.$child->getName(), $keys, true); - if (false === $order || !\is_int($order)) { - continue; - } - $child->setOrderKey($order); + if (!\is_int($orderB)) { + $orderB = $b->getOrderKey(); } + + return $orderA <=> $orderB; + }); + $fieldType->getChildren()->clear(); + foreach ($fields as $field) { + $fieldType->getChildren()->add($field); } $this->logger->notice('log.contenttype.field.reordered', [ 'field_name' => $fieldType->getName(), diff --git a/EMS/core-bundle/src/Form/DataField/DateTimeFieldType.php b/EMS/core-bundle/src/Form/DataField/DateTimeFieldType.php index 0cc332a52..21b1c8081 100644 --- a/EMS/core-bundle/src/Form/DataField/DateTimeFieldType.php +++ b/EMS/core-bundle/src/Form/DataField/DateTimeFieldType.php @@ -103,7 +103,9 @@ public function viewTransform(DataField $dataField) if (\is_string($data) && '' !== $data) { $dateTime = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, $data); - $value = $dateTime ? $dateTime->format(\DateTimeImmutable::ATOM) : null; + $fieldType = $dataField->getFieldType(); + $parseFormat = (null !== $fieldType) ? $fieldType->getDisplayOption('parseFormat') : null; + $value = $dateTime ? $dateTime->format($parseFormat ?? \DateTimeImmutable::ATOM) : null; } return ['value' => $value];