diff --git a/modules/backend/formwidgets/colorpicker/partials/_colorpicker.php b/modules/backend/formwidgets/colorpicker/partials/_colorpicker.php
index 7bdb0e50ca..468f41476c 100644
--- a/modules/backend/formwidgets/colorpicker/partials/_colorpicker.php
+++ b/modules/backend/formwidgets/colorpicker/partials/_colorpicker.php
@@ -31,14 +31,14 @@ class="
data-color-value
class="form-control"
>
- = $value ?>
+ = e($value); ?>
disabled
diff --git a/modules/backend/formwidgets/fileupload/assets/css/fileupload.css b/modules/backend/formwidgets/fileupload/assets/css/fileupload.css
index 2fc811c4e5..91f828c02d 100644
--- a/modules/backend/formwidgets/fileupload/assets/css/fileupload.css
+++ b/modules/backend/formwidgets/fileupload/assets/css/fileupload.css
@@ -63,9 +63,9 @@
.field-fileupload.style-image-multi .upload-object{background:#fff;border:1px solid #ecf0f1;width:260px}
.field-fileupload.style-image-multi .upload-object .progress-bar{display:block;width:100%;overflow:hidden;height:5px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);position:absolute;bottom:10px;left:0}
.field-fileupload.style-image-multi .upload-object .progress-bar .upload-progress{float:left;width:0%;height:100%;line-height:5px;color:#fff;background-color:#5fb6f5;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:width 0.6s ease;transition:width 0.6s ease}
-.field-fileupload.style-image-multi .upload-object .icon-container{border-right:1px solid #f6f8f9;float:left;display:inline-block;overflow:hidden;width:75px;height:75px}
+.field-fileupload.style-image-multi .upload-object .icon-container{border-right:1px solid #f6f8f9;float:left;overflow:hidden;width:75px;height:75px;display:flex;align-items:center;justify-content:center}
.field-fileupload.style-image-multi .upload-object .icon-container i{font-size:35px}
-.field-fileupload.style-image-multi .upload-object .icon-container.image img{border-bottom-left-radius:3px;border-top-left-radius:3px;width:auto}
+.field-fileupload.style-image-multi .upload-object .icon-container.image img{border-bottom-left-radius:3px;border-top-left-radius:3px;width:auto;height:auto;max-height:100%}
.field-fileupload.style-image-multi .upload-object .info{margin-left:90px}
.field-fileupload.style-image-multi .upload-object .info h4{padding-right:15px}
.field-fileupload.style-image-multi .upload-object .info h4 a{right:15px}
@@ -154,4 +154,4 @@
.field-fileupload.style-file-single .upload-object .meta{position:absolute;top:50%;margin-top:-44px;height:88px;right:0;width:15%}
.field-fileupload.style-file-single .upload-object .meta .upload-remove-button{position:absolute;top:50%;right:0;height:20px;margin-top:-10px;margin-right:10px;z-index:100}
.field-fileupload.style-file-single .upload-object .icon-container:after{width:20px;height:20px;margin-top:-10px;margin-left:-10px;background-size:20px 20px}
-.field-fileupload.style-file-single .upload-object.is-error .icon-container:after{font-size:20px}
\ No newline at end of file
+.field-fileupload.style-file-single .upload-object.is-error .icon-container:after{font-size:20px}
diff --git a/modules/backend/formwidgets/fileupload/assets/less/fileupload.imagemulti.less b/modules/backend/formwidgets/fileupload/assets/less/fileupload.imagemulti.less
index 79c2eb32e7..ace43c1882 100644
--- a/modules/backend/formwidgets/fileupload/assets/less/fileupload.imagemulti.less
+++ b/modules/backend/formwidgets/fileupload/assets/less/fileupload.imagemulti.less
@@ -34,11 +34,14 @@
.icon-container {
border-right: 1px solid #f6f8f9;
float: left;
- display: inline-block;
overflow: hidden;
width: 75px;
height: 75px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
i {
font-size: 35px;
}
@@ -46,6 +49,8 @@
&.image img {
.border-left-radius(3px);
width: auto;
+ height: auto;
+ max-height: 100%;
}
}
@@ -131,4 +136,4 @@
width: auto;
}
}
-}
\ No newline at end of file
+}
diff --git a/modules/backend/formwidgets/repeater/assets/css/repeater.css b/modules/backend/formwidgets/repeater/assets/css/repeater.css
index f99a16b536..fddab40d60 100644
--- a/modules/backend/formwidgets/repeater/assets/css/repeater.css
+++ b/modules/backend/formwidgets/repeater/assets/css/repeater.css
@@ -51,4 +51,19 @@
.field-repeater .field-repeater-add-item:focus>a{color:#fff}
.field-repeater .field-repeater-add-item:active{background:#3498db;border-color:transparent}
.field-repeater .field-repeater-add-item:active>a{color:#fff}
-.field-repeater .field-repeater-add-item.in-progress{border-color:#e0e0e0 !important;background:transparent !important}
\ No newline at end of file
+.field-repeater .field-repeater-add-item.in-progress{border-color:#e0e0e0 !important;background:transparent !important}
+.field-repeater[data-mode="grid"]>ul.field-repeater-items{display:grid;gap:20px}
+.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-item{margin-bottom:0 !important}
+.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item{margin-top:0}
+.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item>a{display:flex;flex-direction:column;justify-content:center;height:100%}
+.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item:before{display:none}
+.field-repeater[data-mode="grid"][data-columns="2"] ul.field-repeater-items{grid-template-columns:repeat(2,1fr)}
+.field-repeater[data-mode="grid"][data-columns="3"] ul.field-repeater-items{grid-template-columns:repeat(3,1fr)}
+.field-repeater[data-mode="grid"][data-columns="4"] ul.field-repeater-items{grid-template-columns:repeat(4,1fr)}
+@media (max-width:1600px){.field-repeater[data-mode="grid"][data-columns="4"] ul.field-repeater-items{grid-template-columns:repeat(3,1fr)}}
+.field-repeater[data-mode="grid"][data-columns="5"] ul.field-repeater-items{grid-template-columns:repeat(5,1fr)}
+@media (max-width:1600px){.field-repeater[data-mode="grid"][data-columns="5"] ul.field-repeater-items{grid-template-columns:repeat(4,1fr)}}
+.field-repeater[data-mode="grid"][data-columns="6"] ul.field-repeater-items{grid-template-columns:repeat(6,1fr)}
+@media (max-width:1600px){.field-repeater[data-mode="grid"][data-columns="6"] ul.field-repeater-items{grid-template-columns:repeat(4,1fr)}}
+@media (min-width:768px) and (max-width:1199px){.field-repeater[data-mode="grid"]>ul.field-repeater-items{grid-template-columns:repeat(2,1fr) !important}}
+@media (max-width:767px){.field-repeater[data-mode="grid"]>ul.field-repeater-items{grid-template-columns:1fr !important}.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-item,.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item{min-height:0 !important}.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item{margin-top:10px}.field-repeater[data-mode="grid"]>ul.field-repeater-items>.field-repeater-add-item::before{display:block}}
\ No newline at end of file
diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js
index cc32839d16..1f86457fde 100644
--- a/modules/backend/formwidgets/repeater/assets/js/repeater.js
+++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js
@@ -39,6 +39,7 @@
minItems: null,
maxItems: null,
sortable: false,
+ mode: 'list',
style: 'default',
}
@@ -48,9 +49,9 @@
}
this.$el.on('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess))
- this.$el.on('ajaxDone', '> .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
+ this.$el.on('ajaxDone', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
this.$el.on('click', '> ul > li > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse))
- this.$el.on('click', '> .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
+ this.$el.on('click', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
this.$el.one('dispose-control', this.proxy(this.dispose))
@@ -64,9 +65,9 @@
}
this.$el.off('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess))
- this.$el.off('ajaxDone', '> .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
- this.$el.off('click', '> .field-repeater-items > .field-repeater-item > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse))
- this.$el.off('click', '> .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
+ this.$el.off('ajaxDone', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
+ this.$el.off('click', '> ul > li > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse))
+ this.$el.off('click', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.repeater')
@@ -86,14 +87,15 @@
Repeater.prototype.bindSorting = function() {
var sortableOptions = {
handle: this.options.sortableHandle,
- nested: false
+ nested: false,
+ vertical: this.options.mode === 'list',
}
this.$sortable.sortable(sortableOptions)
}
Repeater.prototype.clickAddGroupButton = function(ev) {
- var $self = this;
+ var $self = this
var templateHtml = $('> [data-group-palette-template]', this.$el).html(),
$target = $(ev.target),
$form = this.$el.closest('form'),
@@ -115,9 +117,14 @@
.on('ajaxPromise', '[data-repeater-add]', function(ev, context) {
$loadContainer.loadIndicator()
- $form.one('ajaxComplete', function() {
+ $(window).one('ajaxUpdateComplete', function() {
$loadContainer.loadIndicator('hide')
$self.togglePrompt()
+ $($self.$el).find('.field-repeater-items > .field-repeater-add-item').each(function () {
+ if ($(this).children().length === 0) {
+ $(this).remove()
+ }
+ })
})
})
@@ -145,15 +152,20 @@
Repeater.prototype.onAddItemSuccess = function(ev) {
window.requestAnimationFrame(() => {
- this.togglePrompt();
+ this.togglePrompt()
$(ev.target).closest('[data-field-name]').trigger('change.oc.formwidget')
- });
+ $(this.$el).find('.field-repeater-items > .field-repeater-add-item').each(function () {
+ if ($(this).children().length === 0) {
+ $(this).remove()
+ }
+ })
+ })
}
Repeater.prototype.togglePrompt = function () {
if (this.options.minItems && this.options.minItems > 0) {
var repeatedItems = this.$el.find('> .field-repeater-items > .field-repeater-item').length,
- $removeItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-item > .repeater-item-remove');
+ $removeItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-item > .repeater-item-remove')
$removeItemBtn.toggleClass('disabled', !(repeatedItems > this.options.minItems))
}
@@ -207,7 +219,7 @@
Repeater.prototype.collapse = function($item) {
$item.addClass('collapsed')
- $('.repeater-item-collapsed-title', $item).text(this.getCollapseTitle($item));
+ $('.repeater-item-collapsed-title', $item).text(this.getCollapseTitle($item))
}
Repeater.prototype.expand = function($item) {
@@ -236,13 +248,13 @@
$target = $item
}
- var $textInput = $('input[type=text]:first, select:first', $target).first();
+ var $textInput = $('input[type=text]:first, select:first', $target).first()
if ($textInput.length) {
switch($textInput.prop("tagName")) {
case 'SELECT':
- return $textInput.find('option:selected').text();
+ return $textInput.find('option:selected').text()
default:
- return $textInput.val();
+ return $textInput.val()
}
} else {
var $disabledTextInput = $('.text-field:first > .form-control', $target)
@@ -255,17 +267,21 @@
}
Repeater.prototype.getStyle = function() {
- var style = 'default';
+ var style = 'default'
// Validate style
if (this.options.style && ['collapsed', 'accordion'].indexOf(this.options.style) !== -1) {
style = this.options.style
}
- return style;
+ return style
}
Repeater.prototype.applyStyle = function() {
+ if (this.options.mode === 'grid') {
+ return
+ }
+
var style = this.getStyle(),
self = this,
items = $(this.$el).children('.field-repeater-items').children('.field-repeater-item')
@@ -318,6 +334,6 @@
$(document).render(function() {
$('[data-control="fieldrepeater"]').fieldRepeater()
- });
+ })
}(window.jQuery);
diff --git a/modules/backend/formwidgets/repeater/assets/less/repeater.less b/modules/backend/formwidgets/repeater/assets/less/repeater.less
index dbacf02d10..ec20f3c438 100644
--- a/modules/backend/formwidgets/repeater/assets/less/repeater.less
+++ b/modules/backend/formwidgets/repeater/assets/less/repeater.less
@@ -1,3 +1,5 @@
+// out: false
+
@import "../../../../assets/less/core/boot.less";
.field-repeater {
@@ -239,4 +241,83 @@
background: transparent !important;
}
}
+
+ &[data-mode="grid"] {
+ > ul.field-repeater-items {
+ display: grid;
+ gap: 20px;
+
+ > .field-repeater-item {
+ margin-bottom: 0 !important;
+ }
+
+ > .field-repeater-add-item {
+ margin-top: 0;
+
+ > a {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 100%;
+ }
+
+ &:before {
+ display: none;
+ }
+ }
+ }
+
+ &[data-columns="2"] ul.field-repeater-items {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ &[data-columns="3"] ul.field-repeater-items {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ &[data-columns="4"] ul.field-repeater-items {
+ grid-template-columns: repeat(4, 1fr);
+
+ @media (max-width: 1600px) {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ }
+ &[data-columns="5"] ul.field-repeater-items {
+ grid-template-columns: repeat(5, 1fr);
+
+ @media (max-width: 1600px) {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ }
+ &[data-columns="6"] ul.field-repeater-items {
+ grid-template-columns: repeat(6, 1fr);
+
+ @media (max-width: 1600px) {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ }
+
+ @media (min-width: @screen-sm-min) and (max-width: @screen-md-max) {
+ > ul.field-repeater-items {
+ grid-template-columns: repeat(2, 1fr) !important;;
+ }
+ }
+
+ @media (max-width: @screen-xs-max) {
+ > ul.field-repeater-items {
+ grid-template-columns: 1fr !important;
+
+ > .field-repeater-item,
+ > .field-repeater-add-item {
+ min-height: 0 !important;
+ }
+
+ > .field-repeater-add-item {
+ margin-top: 10px;
+
+ &::before {
+ display: block;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.php b/modules/backend/formwidgets/repeater/partials/_repeater.php
index 4f70e64ae6..0faf7e6a6d 100644
--- a/modules/backend/formwidgets/repeater/partials/_repeater.php
+++ b/modules/backend/formwidgets/repeater/partials/_repeater.php
@@ -4,6 +4,10 @@
= $minItems ? 'data-min-items="'.$minItems.'"' : '' ?>
= $maxItems ? 'data-max-items="'.$maxItems.'"' : '' ?>
= $style ? 'data-style="'.$style.'"' : '' ?>
+ data-mode="= $mode ?>"
+
+ data-columns="= $columns ?>"
+
data-sortable="true"
data-sortable-container="#= $this->getId('items') ?>"
@@ -14,31 +18,14 @@
$widget): ?>
= $this->makePartial('repeater_item', [
'widget' => $widget,
- 'indexValue' => $index
+ 'indexValue' => $index,
]) ?>
+
+ = $this->makePartial('repeater_add_item') ?>
previewMode): ?>
-
-
diff --git a/modules/backend/formwidgets/repeater/partials/_repeater_add_item.php b/modules/backend/formwidgets/repeater/partials/_repeater_add_item.php
new file mode 100644
index 0000000000..5aac704704
--- /dev/null
+++ b/modules/backend/formwidgets/repeater/partials/_repeater_add_item.php
@@ -0,0 +1,26 @@
+previewMode): ?>
+
+ style="min-height: = $rowHeight ?>px"
+
+ >
+
+
+ = e(trans($prompt)) ?>
+
+
+
+ = e(trans($prompt)) ?>
+
+
+
+
diff --git a/modules/backend/formwidgets/repeater/partials/_repeater_item.php b/modules/backend/formwidgets/repeater/partials/_repeater_item.php
index 2f88eebd59..e18f189a45 100644
--- a/modules/backend/formwidgets/repeater/partials/_repeater_item.php
+++ b/modules/backend/formwidgets/repeater/partials/_repeater_item.php
@@ -4,7 +4,11 @@
?>
- class="field-repeater-item">
+ class="field-repeater-item"
+
+ style="min-height: = $rowHeight ?>px"
+
+>
previewMode): ?>
@@ -27,6 +31,7 @@ class="close"
+
"The model class :model must define a method :method() returning options for the ':field' form field.",
'options_static_method_invalid_value' => "The static method ':method()' on :class did not return a valid options array.",
'colors_method_not_exists' => "The model class :model must define a method :method() returning html color HEX codes for the ':field' form field.",
+ 'colors_invalid_input' => 'The color value supplied is invalid, please try again.',
],
'widget' => [
'not_registered' => "A widget class name ':name' has not been registered",
@@ -319,6 +320,7 @@
'select_page' => 'Select a page...',
],
'relation' => [
+ 'missing_behavior' => 'Field ":field" requires the ":controller" controller to implement the "RelationController" behavior.',
'missing_config' => "Relation behavior does not have any configuration for ':config'.",
'missing_definition' => "Relation behavior does not contain a definition for ':field'.",
'missing_model' => 'Relation behavior used in :class does not have a model defined.',
@@ -327,6 +329,7 @@
'relationwidget_unsupported_type' => 'The ":type" relation type is unsupported by the Relation widget.',
'help' => 'Click on an item to add',
'related_data' => 'Related :name data',
+ 'refresh' => 'Refresh',
'add' => 'Add',
'add_selected' => 'Add selected',
'add_a_new' => 'Add a new :name',
diff --git a/modules/backend/lang/lv/lang.php b/modules/backend/lang/lv/lang.php
index e751c10d46..a005939e6c 100644
--- a/modules/backend/lang/lv/lang.php
+++ b/modules/backend/lang/lv/lang.php
@@ -327,6 +327,7 @@
'relationwidget_unsupported_type' => 'Relāciju logrīks neatbalsta relāciju veidu ":type".',
'help' => 'Noklikšķiniet uz vienuma, lai pievienotu',
'related_data' => 'Saistītie :name dati',
+ 'refresh' => 'Atsvaidzināt',
'add' => 'Pievienot',
'add_selected' => 'Pievienot izvēlētos',
'add_a_new' => 'Pievienot jaunu :name',
diff --git a/modules/backend/lang/ru/lang.php b/modules/backend/lang/ru/lang.php
index 1a73058184..b102c1cc4f 100644
--- a/modules/backend/lang/ru/lang.php
+++ b/modules/backend/lang/ru/lang.php
@@ -222,7 +222,7 @@
'setup_title' => 'Настройка списка',
'setup_help' => 'Используйте флажки для выбора колонок, которые вы хотите видеть в списке. Вы можете изменить положение столбцов, перетаскивая их вверх или вниз.',
'records_per_page' => 'Записей на странице',
- 'records_per_page_help' => 'Выберите количество записей на странице для отображения. Обратите внимание, что большое количество записей на одной странице может привести к снижению производительности.',
+ 'records_per_page_help' => 'Выберите количество записей для отображения на странице. Обратите внимание, что большое количество записей на одной странице может привести к снижению производительности.',
'check' => 'Проверить',
'delete_selected' => 'Удалить выбранное',
'delete_selected_empty' => 'Нет выбранных записей для удаления.',
@@ -319,6 +319,7 @@
'select_page' => 'Выберите страницу...',
],
'relation' => [
+ 'missing_behavior' => 'Поле ":field" требует, чтобы контроллер ":controller" реализовывал поведение "RelationController".',
'missing_config' => "Поведение отношения не имеет конфигурации для ':config'.",
'missing_definition' => "Поведение отношения не содержит определения для ':field'.",
'missing_model' => 'Для поведения отношения, используемого в :class не определена модель.',
@@ -327,6 +328,7 @@
'relationwidget_unsupported_type' => '":type" тип связи не поддерживается виджетом Relation.',
'help' => 'Нажмите на элемент, который нужно добавить',
'related_data' => 'Связанные :name данные',
+ 'refresh' => 'Обновить',
'add' => 'Добавить',
'add_selected' => 'Добавить выбранные',
'add_a_new' => 'Добавить новый :name',
@@ -495,7 +497,7 @@
'menu_label' => 'Журнал доступа',
'menu_description' => 'Просмотр списка успешных авторизаций администраторов.',
'id' => 'ID',
- 'created_at' => 'Дата & Время',
+ 'created_at' => 'Дата и время',
'type' => 'Тип',
'login' => 'Логин',
'ip_address' => 'IP-адрес',
@@ -507,7 +509,7 @@
'all' => 'все',
'options_method_not_exists' => "Модель класса :model должна определить метод :method() возвращающего варианты для фильтра ':filter'.",
'date_all' => 'весь период',
- 'number_all' => 'все номера',
+ 'number_all' => 'все числа',
],
'import_export' => [
'upload_csv_file' => '1. Загрузка CSV-файла',
diff --git a/modules/backend/lang/tr/lang.php b/modules/backend/lang/tr/lang.php
index 9a8362b769..d7719a9b9a 100644
--- a/modules/backend/lang/tr/lang.php
+++ b/modules/backend/lang/tr/lang.php
@@ -9,6 +9,7 @@
'invalid_type' => 'Geçersiz alan tipi :type.',
'options_method_invalid_model' => "':field' metodu, geçerli bir model ile eşleşmiyor. :model Model'i için options metodu tanımlamalısınız.",
'options_method_not_exists' => ":model Model'i içerisinde ':field' formuna geri dönüş için bir :method() metodu tanımlanmalıdır.",
+ 'options_static_method_invalid_value' => ":class içindeki ':method()' static metodu geçerli bir options array'i döndermiyor.",
'colors_method_not_exists' => ":model Model'i içerisinde ':field' form alanı için html renk HEX kodu üreten :method() metodu tanımlanmalıdır.",
],
'widget' => [
@@ -17,6 +18,11 @@
],
'page' => [
'untitled' => "Başlıksız",
+ '404' => [
+ 'label' => 'Sayfa Bulunamadı',
+ 'help' => "İstenilen URL bulunamadı. Belki de başka bir sayfayı arıyorsunuz?",
+ 'back_link' => 'Önceki sayfaya dön',
+ ],
'access_denied' => [
'label' => "Giriş engellendi",
'help' => "Bu sayfayı görüntülemek için gerekli izinlere sahip değilsiniz.",
@@ -30,23 +36,38 @@
],
'partial' => [
'not_found_name' => "':name' bölümü bulunamadı.",
+ 'invalid_name' => 'Partial ismi geçersiz: :name.',
+ ],
+ 'ajax_handler' => [
+ 'invalid_name' => 'AJAX işleyicisi ismi geçersiz: :name.',
+ 'not_found' => "AJAX işleyicisi ':name' bulunamadı.",
],
'account' => [
+ 'impersonate' => 'Kullanıcının kimliğine bürün',
+ 'impersonate_confirm' => 'Bu kullanıcının kimliğine bürünmek istediğinizden emin misiniz? Oturumu kapatarak orijinal durumunuza geri dönebilirsiniz.',
+ 'impersonate_success' => 'Artık bu kullanıcının kimliğine bürünüyorsunuz',
+ 'impersonate_working' => 'İşleniyor...',
+ 'impersonating' => 'Geçici olarak :impersonatee ile oturum açtınız. Kayıtlar sizi hâlâ :impersonator olarak tanımlayacak',
+ 'stop_impersonating' => 'Kimliğe bürünmeyi bırak',
+ 'unsuspend' => 'Askıyı kaldır',
+ 'unsuspend_confirm' => 'Bu kullanıcının askıya alınmasını kaldırmak istediğinizden emin misiniz?',
+ 'unsuspend_success' => 'Kullanıcının askıya alınması kaldırıldı.',
+ 'unsuspend_working' => 'İşleniyor...',
'signed_in_as' => ':full_name olarak giriş yapıldı',
'sign_out' => 'Çıkış',
'login' => 'Giriş',
'reset' => 'Sıfırla',
'restore' => 'Geri yükle',
- 'login_placeholder' => 'kullanıcı adı',
- 'password_placeholder' => 'şifre',
+ 'login_placeholder' => 'Kullanıcı adı',
+ 'password_placeholder' => 'Şifre',
'remember_me' => 'Beni hatırla',
'forgot_password' => "Şifrenizi mi unuttunuz?",
- 'enter_email' => "Email adresinizi girin",
+ 'enter_email' => "E-mail adresinizi girin",
'enter_login' => "Kullanıcı adınızı girin",
- 'email_placeholder' => "email",
+ 'email_placeholder' => "E-mail",
'enter_new_password' => "Yeni Şifrenizi girin",
'password_reset' => "Şifre Sıfırla",
- 'restore_success' => "Email adresinize şifrenizi nasıl sıfırlayacağınıza dair bilgiler gönderildi.",
+ 'restore_success' => "E-mail adresinize şifrenizi nasıl sıfırlayacağınıza dair bilgiler gönderildi.",
'reset_success' => "Şifreniz başarıyla sıfırlandı. Giriş yapabilirsiniz.",
'reset_error' => "Hatalı giriş yaptınız. Lütfen tekrar deneyin!",
'reset_fail' => "Şifre sıfırlanamadı!",
@@ -54,12 +75,16 @@
'cancel' => 'İptal',
'delete' => 'Sil',
'ok' => 'Tamam',
+ 'sending' => 'Gönderiliyor...',
+ 'password_reset_email' => 'Şifre sıfırlama e-postası gönder',
+ 'manual_password_reset_confirm' => 'Bu kullanıcıya şifre sıfırlama e-postası göndermek istediğinizden emin misiniz?',
+ 'manual_password_reset_success' => 'Kullanıcıya şifresini sıfırlama talimatlarını içeren bir e-posta gönderildi.',
],
'dashboard' => [
'menu_label' => 'Anasayfa',
'widget_label' => 'Eklenti',
'widget_width' => 'Genişlik',
- 'full_width' => 'tam genişlik',
+ 'full_width' => 'Tam genişlik',
'manage_widgets' => 'Eklentileri yönet',
'add_widget' => 'Eklenti ekle',
'widget_inspector_title' => 'Eklenti ayarları',
@@ -114,7 +139,7 @@
'first_name' => "İsim",
'last_name' => "Soyisim",
'full_name' => "Tam Adı",
- 'email' => "Email",
+ 'email' => "E-mail",
'role_field' => 'Roller',
'role_comment' => 'Roller kullanıcı izinlerini tanımlar. Bu roller, izinler sekmesinden kullanıcı düzeyinde değiştirilebilir.',
'groups' => "Gruplar",
@@ -126,7 +151,7 @@
'account' => 'Hesap',
'superuser' => "Süper Kullanıcı",
'superuser_comment' => "Kullanıcıya her alanda yetki vermek için burayı işaretleyin.",
- 'send_invite' => 'Email ile davet gönder',
+ 'send_invite' => 'E-mail ile davet gönder',
'send_invite_comment' => 'Kullanıcının email adresine davet göndermek için burayı işaretleyin',
'delete_confirm' => 'Bu yöneticiyi silmek istiyor musunuz?',
'return' => 'Yöneticiler listesine dön',
@@ -219,6 +244,7 @@
'remove_file' => 'Dosyayı sil',
],
'repeater' => [
+ 'add_new_item' => 'Yeni öğe ekle',
'min_items_failed' => ':name için en az :min nesne gerekli, sadece :items nesne tanımlandı',
'max_items_failed' => ':name için en fazla :max nesne tanımlanabilir, :items nesne tanımlandı',
],
@@ -252,7 +278,7 @@
'restore' => 'Geri yükle',
'restoring' => 'Geri yükleniyor',
'confirm_restore' => 'Bu kaydı geri yüklemek istediğinize emin misiniz?',
- 'reset_default' => 'Ön Tanımlı Ayarlara Dön!',
+ 'reset_default' => 'Tüm Ayarları Sıfırla!',
'resetting' => 'İşleniyor',
'resetting_name' => ':name İşleniyor',
'undefined_tab' => 'Çeşitli',
@@ -285,6 +311,7 @@
],
'recordfinder' => [
'find_record' => 'Kayıt Bul',
+ 'invalid_model_class' => 'Kayıt bulucu için sağlanan ":modelClass" model sınıfı geçersiz',
'cancel' => 'İptal',
],
'pagelist' => [
@@ -292,13 +319,16 @@
'select_page' => 'Sayfa seçin...',
],
'relation' => [
+ 'missing_behavior' => '":field" alanı, "RelationController" davranışını uygulamak için ":controller" controllerinin bulunmasını gerektirir.',
'missing_config' => "İlişki ':config' için bir yapılandırma ayarı içermiyor.",
'missing_definition' => "İlişki ':field' için bir sütun değeri içermiyor.",
'missing_model' => ":class da kullanılan ilişki için model değeri tanımlanmamış.",
'invalid_action_single' => "Bu işlem tekli ilişkilendirme için kullanılamaz.",
'invalid_action_multi' => "Bu işlem çoklu ilişkilendirme için kullanılamaz.",
+ 'relationwidget_unsupported_type' => '"Relation" bileşeni tarafından ":type" ilişki türü desteklenmemektedir.',
'help' => 'Eklemek için bir öğeye tıklayın',
'related_data' => 'İlişkili veri :name',
+ 'refresh' => 'Yenile',
'add' => "Ekle",
'add_selected' => 'Seçilenleri ekle',
'add_a_new' => 'Yeni bir :name ekle',
@@ -343,10 +373,14 @@
'permissions' => ':name dizini ve alt dizinleri PHP tarafından yazılabilir değil. Lütfen bu dizindeki webserver için gerekli yazma izinlerini verin.',
'extension' => ':name PHP eklentisi sistemde yüklü değil. Lütfen kütüphaneyi kurun ve eklentiyi aktifleştirin.',
'plugin_missing' => ':name isimli eklenti gerekli, fakat yüklenmemiş. Lütfen bu eklentiyi yükleyin.',
+ 'debug' => 'Hata ayıklama modu etkin. Bu, canlı ortam kurulumları için önerilmez.',
+ 'decompileBackendAssets' => 'Backend için assetler şu anda kaynak koda dönüştürülmüş durumunda. Bu, canlı ortam kurulumları için önerilmez',
+ 'default_backend_user' => 'Varsayılan giriş bilgilerine sahip bir yönetici bulundu (admin / admin@domain.tld). Sistemi korumak için kullanıcı adını ve/veya e-posta adresini değiştirin.',
],
'editor' => [
'menu_label' => 'Editör ayarları',
'menu_description' => 'Metin editörü ayarlarını düzenleyebilirsiniz.',
+ 'preview' => 'Önizleme',
'font_size' => 'Font büyüklüğü',
'tab_size' => 'Tab genişliği',
'use_hard_tabs' => 'Tab girintisi',
@@ -381,18 +415,30 @@
'label' => 'Etiket',
'class_name' => 'Class ismi',
'markup_tags' => 'Markup Tagları',
+ 'markup_tag' => 'Markup Tag',
'allowed_empty_tags' => 'İzin verilen boş taglar',
'allowed_empty_tags_comment' => 'İçeriği olmasada kaydederken silinmeyen, bulunmasına izin verilen taglar.',
'allowed_tags' => 'İzin verilen taglar',
'allowed_tags_comment' => 'İzin verilen taglar listesi.',
+ 'allowed_attributes' => 'İzin verilen attributelar',
+ 'allowed_attributes_comment' => 'İzin verilen attribute listesi.',
'no_wrap' => 'Tagları sarmalama',
'no_wrap_comment' => 'Tag blokları içinde sarmalanmayacak taglar listesi.',
'remove_tags' => 'Silinecek taglar',
'remove_tags_comment' => 'İçeriği ile birlikte silinecek taglar listesi.',
'line_breaker_tags' => 'Satır atlatma etiketleri',
'line_breaker_tags_comment' => 'Aralarına bir satır atlatma öğesi yerleştirmek için kullanılan etiketlerin listesi.',
+ 'toolbar_options' => 'Araç Çubuğu Seçenekleri',
'toolbar_buttons' => 'Araç Çubuğu Düğmeleri',
'toolbar_buttons_comment' => 'Rich Editor\'de varsayılan olarak görüntülenecek Araç Çubuğu düğmeleri.',
+ 'toolbar_buttons_preset' => 'Önceden ayarlanmış bir araç çubuğu düğme yapılandırmasını ekleyin:',
+ 'toolbar_buttons_presets' => [
+ 'default' => 'Ön tanımlı',
+ 'minimal' => 'En az öğe',
+ 'full' => 'Tüm öğeler',
+ ],
+ 'paragraph_formats' => 'Paragraf Formatları',
+ 'paragraph_formats_comment' => 'Paragraf Formatı açılır menüsünde görünecek seçenekler.',
],
'tooltips' => [
'preview_website' => 'Websiteyi Önizle',
@@ -412,19 +458,27 @@
'brand' => 'Marka',
'logo' => 'Logo',
'logo_description' => 'Yönetim Panelinde kullanılacak logoyu yükleyin.',
+ 'favicon' => 'Favicon',
+ 'favicon_description' => 'Yönetim Panelinde kullanılacak favicon yükleyin',
'app_name' => 'Site Adı',
'app_name_description' => 'Bu isim, Yönetim Panelinde başlık olarak kullanılacaktır.',
'app_tagline' => 'Site Mottosu',
'app_tagline_description' => 'Bu motto Yönetim Paneli giriş ekranında görüntülenecektir.',
'colors' => 'Renkler',
- 'primary_color' => 'Ana Renk color',
- 'secondary_color' => 'İkincil Renk color',
- 'accent_color' => 'Accent color',
+ 'branding_colors' => 'Marka renkleri',
+ 'branding_colors_comment' => 'Bu renkler, markanıza uyacak şekilde Panel arayüzü genelinde kullanılacaktır.',
+ 'default_colors' => 'Varsayılan renkler',
+ 'default_colors_comment' => 'Bu renkler, geçersiz kılınmadığı sürece tüm renk seçicilerde renk örnekleri olarak kullanılacaktır.',
+ 'add_default_color' => 'Varsayılan bir renk ekle',
+ 'primary_color' => 'Ana Renk',
+ 'secondary_color' => 'İkincil Renk',
+ 'accent_color' => 'Aksan rengi',
'styles' => 'Stiller',
'custom_stylesheet' => 'Özel stil - CSS',
- 'navigation' => 'Navigsyon',
+ 'navigation' => 'Navigasyon',
'menu_mode' => 'Menü stili',
'menu_mode_inline' => 'Sıralı',
+ 'menu_mode_inline_no_icons' => 'Sıralı (ikonsuz)',
'menu_mode_tile' => 'Mozaik',
'menu_mode_collapsed' => 'Katlanmış',
],
@@ -449,7 +503,7 @@
'ip_address' => 'IP adres',
'first_name' => 'İsim',
'last_name' => 'Soyisim',
- 'email' => 'Email',
+ 'email' => 'E-mail',
],
'filter' => [
'all' => 'tümü',
@@ -526,16 +580,19 @@
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Baltık Rim)',
'iso_8859_14' => 'ISO-8859-14 (Latin-8, Celtic Dili)',
'iso_8859_15' => 'ISO-8859-15 (Latin-9, Euro işareti revizyonlu Batı Avrupa)',
+ 'windows_1250' => 'Windows-1250 (CP1250, Orta ve Doğu Avrupa)',
'windows_1251' => 'Windows-1251 (CP1251)',
'windows_1252' => 'Windows-1252 (CP1252)',
],
],
'permissions' => [
- 'manage_media' => 'Medyaları düzenleyebilsin',
+ 'manage_media' => 'Medya içeriklerini (resimler, videolar, sesler, belgeler) yükleyip düzenleyebilsin',
+ 'allow_unsafe_markdown' => 'Güvenli olmayan Markdown kullanabilsin (Javascript içerebilir)',
],
'mediafinder' => [
'label' => 'Medya Bulucu',
'default_prompt' => 'Bir medya öğesi bulmak için %s butonuna tıklayın',
+ 'no_image' => 'Resim bulunamadı',
],
'media' => [
'menu_label' => 'Medya',
diff --git a/modules/backend/layouts/_head.php b/modules/backend/layouts/_head.php
index bcaaa29154..f1b4526c21 100644
--- a/modules/backend/layouts/_head.php
+++ b/modules/backend/layouts/_head.php
@@ -29,7 +29,7 @@
Url::asset('modules/system/assets/js/build/manifest.js'),
Url::asset('modules/system/assets/js/snowboard/build/snowboard.vendor.js'),
Url::asset(
- (Config::get('develop.debugSnowboard', Config::get('app.debug', false)) === true)
+ (Config::get('develop.debugSnowboard', false) === true)
? 'modules/system/assets/js/build/system.debug.js'
: 'modules/system/assets/js/build/system.js'
),
diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php
index cb8dce833b..4063c716c2 100644
--- a/modules/backend/models/User.php
+++ b/modules/backend/models/User.php
@@ -46,7 +46,7 @@ class User extends UserBase
* Relations
*/
public $belongsToMany = [
- 'groups' => [UserGroup::class, 'table' => 'backend_users_groups']
+ 'groups' => [UserGroup::class, 'table' => 'backend_users_groups', 'softDelete' => true]
];
public $belongsTo = [
diff --git a/modules/backend/tests/formwidgets/ColorPickerTest.php b/modules/backend/tests/formwidgets/ColorPickerTest.php
new file mode 100644
index 0000000000..c97200441a
--- /dev/null
+++ b/modules/backend/tests/formwidgets/ColorPickerTest.php
@@ -0,0 +1,128 @@
+makeWidget();
+
+ // Default only expects hex
+ $this->assertEquals('#3498DB', $widget->getSaveValue('#3498DB'));
+
+ // Getting a non-hex value should throw an exception
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('rgba(51.9, 152, 219, 1)');
+
+ // Test a bunch of hex values
+ $this->assertEquals('#3498DB', $widget->getSaveValue('#3498DB'));
+ $this->assertEquals('#2980B9', $widget->getSaveValue('#2980B9'));
+ $this->assertEquals('#9B59B6', $widget->getSaveValue('#9B59B6'));
+ }
+
+ public function testRgbSaveValue(): void
+ {
+ $widget = $this->makeWidget([
+ 'formats' => 'rgb'
+ ]);
+
+ // Config specifies only rgb
+ $this->assertEquals('rgba(51.9, 152, 219, 1)', $widget->getSaveValue('rgba(51.9, 152, 219, 1)'));
+
+ // Getting a non-rgb value should throw an exception
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('#3498DB');
+
+ // Test a bunch of rgb values
+ $this->assertEquals('rgba(1, 1, 1, 1)', $widget->getSaveValue('rgba(1, 1, 1, 1)'));
+ $this->assertEquals('rgba(155, 89, 182, 0.5)', $widget->getSaveValue('rgba(155, 89, 182, 0.5)'));
+ $this->assertEquals('rgba(1, 89, 182, 0.55)', $widget->getSaveValue('rgba(1, 89, 182, 0.55)'));
+ }
+
+ public function testCmykSaveValue(): void
+ {
+ $widget = $this->makeWidget([
+ 'formats' => 'cmyk'
+ ]);
+
+ // Config specifies only cmyk
+ $this->assertEquals('cmyk(76.3%, 30.6%, 0%, 14.1%)', $widget->getSaveValue('cmyk(76.3%, 30.6%, 0%, 14.1%)'));
+
+ // Getting a non-cmyk value should throw an exception
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('#3498DB');
+
+ // Test a bunch of cmyk values
+ $this->assertEquals('cmyk(14.8%, 51.1%, 0%, 28.6%)', $widget->getSaveValue('cmyk(14.8%, 51.1%, 0%, 28.6%)'));
+ $this->assertEquals('cmyk(17%, 60.75%, 0%, 32.22%)', $widget->getSaveValue('cmyk(17%, 60.75%, 0%, 32.22%)'));
+ $this->assertEquals('cmyk(17.9%, 60.75%, 0%, 32.2%)', $widget->getSaveValue('cmyk(17.9%, 60.75%, 0%, 32.2%)'));
+ }
+
+ public function testHslaSaveValue(): void
+ {
+ $widget = $this->makeWidget([
+ 'formats' => 'hsl'
+ ]);
+
+ // Config specifies only hsl
+ $this->assertEquals('hsla(204.1, 69.9%, 53.1%, 1)', $widget->getSaveValue('hsla(204.1, 69.9%, 53.1%, 1)'));
+
+ // Getting a non-hsl value should throw an exception
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('#3498DB');
+
+ // Test a bunch of hsl values
+ $this->assertEquals('hsla(282.3, 43.6%, 47.2%, 1)', $widget->getSaveValue('hsla(282.3, 43.6%, 47.2%, 1)'));
+ $this->assertEquals('hsla(282.3, 43.6%, 47.2%, 0.1)', $widget->getSaveValue('hsla(282.3, 43.6%, 47.2%, 0.1)'));
+ $this->assertEquals('hsla(282, 43.6%, 47.2%, 0.1)', $widget->getSaveValue('hsla(282, 43.6%, 47.2%, 0.1)'));
+ $this->assertEquals('hsla(282, 43.56%, 47.2%, 0.1)', $widget->getSaveValue('hsla(282, 43.56%, 47.2%, 0.1)'));
+ $this->assertEquals('hsla(282.22, 43%, 47.2%, 0.1)', $widget->getSaveValue('hsla(282.22, 43%, 47.2%, 0.1)'));
+ }
+
+ public function testAllSaveValue(): void
+ {
+ $widget = $this->makeWidget([
+ 'formats' => 'all'
+ ]);
+
+ // Config allows for any valid format
+ $this->assertEquals('#3498DB', $widget->getSaveValue('#3498DB'));
+ $this->assertEquals('rgba(51.9, 152, 219, 1)', $widget->getSaveValue('rgba(51.9, 152, 219, 1)'));
+ $this->assertEquals('cmyk(76.3%, 30.6%, 0%, 14.1%)', $widget->getSaveValue('cmyk(76.3%, 30.6%, 0%, 14.1%)'));
+ $this->assertEquals('hsla(204.1, 69.9%, 53.1%, 1)', $widget->getSaveValue('hsla(204.1, 69.9%, 53.1%, 1)'));
+
+ // Getting a invalid value should throw an exception
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('#Winter Is Awesome');
+
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('rgba(51.9, 152, 219, 1) -- test');
+
+ $this->expectException(ApplicationException::class);
+ $widget->getSaveValue('Test(51.9, 152, 219, 1)');
+ }
+
+ public function testAllowCustomSaveValue(): void
+ {
+ $widget = $this->makeWidget([
+ 'formats' => 'custom'
+ ]);
+
+ // Config allows for any format
+ $this->assertEquals('rgba(51.9, 152, 219, 1)', $widget->getSaveValue('rgba(51.9, 152, 219, 1)'));
+ $this->assertEquals('#Winter Is Awesome', $widget->getSaveValue('#Winter Is Awesome'));
+ $this->assertEquals('Test(51.9, 152, 219, 1)', $widget->getSaveValue('Test(51.9, 152, 219, 1)'));
+ }
+
+ protected function makeWidget(array $config = []): ColorPicker
+ {
+ return new ColorPicker(new Controller(), new FormField('test', 'Test'), $config);
+ }
+}
diff --git a/modules/backend/widgets/Form.php b/modules/backend/widgets/Form.php
index d182570a63..6aed0c2834 100644
--- a/modules/backend/widgets/Form.php
+++ b/modules/backend/widgets/Form.php
@@ -1177,6 +1177,7 @@ protected function showFieldLabels($field)
public function getSaveData()
{
$this->defineFormFields();
+ $this->applyFiltersFromModel();
$result = [];
@@ -1342,6 +1343,11 @@ public function getOptionsFromModel($field, $fieldOptions)
]));
}
return $result;
+ } else {
+ // Handle localization keys that return arrays
+ if (is_array($options = Lang::get($fieldOptions))) {
+ return $options;
+ }
}
}
diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php
index 0b78b483c0..85bec9b108 100644
--- a/modules/backend/widgets/Lists.php
+++ b/modules/backend/widgets/Lists.php
@@ -510,7 +510,9 @@ public function prepareQuery()
$relationObj = $this->model->{$column->relation}();
$countQuery = $relationObj->getRelationExistenceQuery($relationObj->getRelated()->newQueryWithoutScopes(), $query);
- $joinSql = $this->isColumnRelated($column, true)
+ $limit = $column->config['limit'] ?? false;
+
+ $joinSql = $this->isColumnRelated($column, true) && $limit !== 1
? DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ')")
: DbDongle::raw($sqlSelect);
@@ -520,6 +522,10 @@ public function prepareQuery()
$joinQuery->whereRaw(DbDongle::parse($column->config['conditions']));
}
+ if ($limit) {
+ $joinQuery->limit($column->config['limit']);
+ }
+
$joinSql = $joinQuery->toSql();
$selects[] = Db::raw("(".$joinSql.") as ".$alias);
@@ -792,6 +798,57 @@ protected function defineListColumns()
throw new ApplicationException(Lang::get('backend::lang.list.missing_columns', compact('class')));
}
+ /**
+ * @event backend.list.extendColumnsBefore
+ * Provides an opportunity to modify the columns of a List widget before the columns are created.
+ *
+ * Example usage:
+ *
+ * Event::listen('backend.list.extendColumnsBefore', function ($listWidget) {
+ * // Only for the User controller
+ * if (!$listWidget->getController() instanceof \Backend\Controllers\Users) {
+ * return;
+ * }
+ *
+ * // Only for the User model
+ * if (!$listWidget->model instanceof \Backend\Models\User) {
+ * return;
+ * }
+ *
+ * // Add a column in first position
+ * $listWidget->columns = array_merge([
+ * 'myColumn' => [
+ * 'type' => 'text',
+ * 'label' => 'My Column',
+ * ],
+ * ], $listWidget->columns);
+ * });
+ *
+ * Or
+ *
+ * $listWidget->bindEvent('list.extendColumnsBefore', function () use ($listWidget) {
+ * // Only for the User controller
+ * if (!$listWidget->getController() instanceof \Backend\Controllers\Users) {
+ * return;
+ * }
+ *
+ * // Only for the User model
+ * if (!$listWidget->model instanceof \Backend\Models\User) {
+ * return;
+ * }
+ *
+ * // Add a column in first position
+ * $listWidget->columns = array_merge([
+ * 'myColumn' => [
+ * 'type' => 'text',
+ * 'label' => 'My Column',
+ * ],
+ * ], $listWidget->columns);
+ * });
+ *
+ */
+ $this->fireSystemEvent('backend.list.extendColumnsBefore');
+
$this->addColumns($this->columns);
/**
@@ -1053,6 +1110,10 @@ public function getColumnValueRaw($record, $column)
}
}
+ if ($value instanceof \BackedEnum) {
+ $value = $value->value;
+ }
+
/**
* @event backend.list.overrideColumnValueRaw
* Overrides the raw column value in a list widget.
@@ -1479,10 +1540,19 @@ public function addFilter(callable $filter)
public function setSearchTerm($term, $resetPagination = false)
{
if (
- strlen($this->searchTerm) !== 0
- && trim($this->searchTerm) !== ''
+ strlen($term) !== 0
+ && trim($term) !== ''
) {
+ if ($this->showTree === true) {
+ // save initial list config showTree value
+ $this->putSession('showTree', true);
+ }
$this->showTree = false;
+ } else {
+ if ($this->getSession('showTree')) {
+ // restore initial list config showTree value
+ $this->showTree = true;
+ }
}
if ($resetPagination) {
diff --git a/modules/backend/widgets/MediaManager.php b/modules/backend/widgets/MediaManager.php
index 8bb5b6fee0..067d53a50d 100644
--- a/modules/backend/widgets/MediaManager.php
+++ b/modules/backend/widgets/MediaManager.php
@@ -21,6 +21,7 @@
class MediaManager extends WidgetBase
{
use \Backend\Traits\UploadableWidget;
+ use \Backend\Traits\PreferenceMaker;
const FOLDER_ROOT = '/';
@@ -86,9 +87,8 @@ protected function loadAssets()
/**
* Abort the request with an access-denied code if readOnly mode is active
- * @return void
*/
- protected function abortIfReadOnly()
+ protected function abortIfReadOnly(): void
{
if ($this->readOnly) {
abort(403);
@@ -97,9 +97,8 @@ protected function abortIfReadOnly()
/**
* Renders the widget.
- * @return string
*/
- public function render()
+ public function render(): string
{
$this->prepareVars();
@@ -111,7 +110,7 @@ public function render()
//
/**
- * Perform search AJAX handler
+ * Perform a search with the query specified in the request ("search")
*/
public function onSearch(): array
{
@@ -126,7 +125,7 @@ public function onSearch(): array
}
/**
- * Change view AJAX handler
+ * Go to the path specified in the request ("path")
*/
public function onGoToFolder(): array
{
@@ -150,7 +149,7 @@ public function onGoToFolder(): array
}
/**
- * Generate thumbnail AJAX handler
+ * Generate thumbnails for the provided array of thumbnail info ("batch")
*/
public function onGenerateThumbnails(): array
{
@@ -170,7 +169,7 @@ public function onGenerateThumbnails(): array
}
/**
- * Get thumbnail AJAX handler
+ * Get the thumbnail for the provided path ("path") and lastModified date ("lastModified")
*
* @throws ApplicationException if the lastModified date is invalid
*/
@@ -197,7 +196,7 @@ public function onGetSidebarThumbnail(): array
}
/**
- * Set view preference AJAX handler
+ * Render the view for the provided "path" and "view" mode from the request
*/
public function onChangeView(): array
{
@@ -217,7 +216,7 @@ public function onChangeView(): array
}
/**
- * Set filter preference AJAX handler
+ * Set the current filter from the request ("filter")
*/
public function onSetFilter(): array
{
@@ -237,7 +236,7 @@ public function onSetFilter(): array
}
/**
- * Set sorting preference AJAX handler
+ * Set the current sorting configuration from the request ("sortBy", "sortDirection")
*/
public function onSetSorting(): array
{
@@ -258,7 +257,7 @@ public function onSetSorting(): array
}
/**
- * Delete library item AJAX handler
+ * Deletes the provided paths from the request ("paths")
*
* @throws ApplicationException if the paths input is invalid
* @todo Move media events to the MediaLibary class instead.
@@ -356,7 +355,7 @@ public function onDeleteItem(): array
}
/**
- * Show rename item popup AJAX handler
+ * Render the rename popup for the provided "path" from the request
*/
public function onLoadRenamePopup(): string
{
@@ -374,7 +373,7 @@ public function onLoadRenamePopup(): string
}
/**
- * Rename library item AJAX handler
+ * Rename the provided path from the request ("originalPath") to the new name ("name")
*
* @throws ApplicationException if the new name is invalid
* @todo Move media events to the MediaLibary class instead.
@@ -458,7 +457,7 @@ public function onApplyName(): void
}
/**
- * Create library folder AJAX handler
+ * Create a new folder ("name") in the provided "path" from the request
*
* @throws ApplicationException If the requested folder already exists or is otherwise invalid
*/
@@ -522,7 +521,7 @@ public function onCreateFolder(): array
}
/**
- * Show move item popup AJAX handler
+ * Render the move popup with a list of folders to move the selected items to excluding the provided paths in the request ("exclude")
*
* @throws ApplicationException If the exclude input data is not an array
*/
@@ -558,7 +557,7 @@ public function onLoadMovePopup(): string
}
/**
- * Move library item AJAX handler
+ * Move the selected items ("files", "folders") to the provided destination path from the request ("dest")
*
* @throws ApplicationException if the input data is invalid
*/
@@ -650,7 +649,7 @@ public function onMoveItems(): array
}
/**
- * Sidebar visibility AJAX handler
+ * Sets the sidebar visibility state from the request ("visible")
*/
public function onSetSidebarVisible(): void
{
@@ -660,7 +659,7 @@ public function onSetSidebarVisible(): void
}
/**
- * Renders the widget in a popup body
+ * Renders the widget in a popup body (options include "bottomToolbar" and "cropAndInsertButton")
*/
public function onLoadPopup(): string
{
@@ -905,11 +904,9 @@ protected function findFiles($searchTerm, $filter, $sortBy)
}
/**
- * Sets the user current folder from the session state
- *
- * @param string $path
+ * Sets the provided path as the current folder in the session
*/
- protected function setCurrentFolder($path): void
+ protected function setCurrentFolder(string $path): void
{
$path = MediaLibrary::validatePath($path);
@@ -917,21 +914,17 @@ protected function setCurrentFolder($path): void
}
/**
- * Gets the user current folder from the session state
- *
- * @return string
+ * Gets the user's current folder from the session
*/
- protected function getCurrentFolder()
+ protected function getCurrentFolder(): string
{
return $this->getSession('media_folder', self::FOLDER_ROOT);
}
/**
- * Sets the user filter from the session state
- *
- * @param string $filter
+ * Sets the user filter from the session
*/
- protected function setFilter($filter): void
+ protected function setFilter(string $filter): void
{
if (!in_array($filter, [
self::FILTER_ALL,
@@ -984,20 +977,16 @@ protected function setSearchTerm($searchTerm): void
/**
* Gets the user search term from the session state
- *
- * @return string
*/
- protected function getSearchTerm()
+ protected function getSearchTerm(): ?string
{
return $this->getSession('media_search', null);
}
/**
- * Sets the user sort column from the session state
- *
- * @param string $sortBy
+ * Sets the sort column
*/
- protected function setSortBy($sortBy): void
+ protected function setSortBy(string $sortBy): void
{
if (!in_array($sortBy, [
MediaLibrary::SORT_BY_TITLE,
@@ -1007,21 +996,22 @@ protected function setSortBy($sortBy): void
throw new ApplicationException('Invalid input data');
}
- $this->putSession('media_sort_by', $sortBy);
+ $key = 'media_sort_by';
+ $this->putUserPreference($key, $sortBy);
+ $this->putSession($key, $sortBy);
}
/**
- * Gets the user sort column from the session state
- *
- * @return string
+ * Gets the current column to sort by
*/
- protected function getSortBy()
+ protected function getSortBy(): string
{
- return $this->getSession('media_sort_by', MediaLibrary::SORT_BY_TITLE);
+ $key = 'media_sort_by';
+ return $this->getSession($key, $this->getUserPreference($key, MediaLibrary::SORT_BY_TITLE));
}
/**
- * Sets the user sort direction from the session state
+ * Sets the sort direction from the session state
*
* @param string $sortDirection
*/
@@ -1034,17 +1024,18 @@ protected function setSortDirection($sortDirection): void
throw new ApplicationException('Invalid input data');
}
- $this->putSession('media_sort_direction', $sortDirection);
+ $key = 'media_sort_direction';
+ $this->putUserPreference($key, $sortDirection);
+ $this->putSession($key, $sortDirection);
}
/**
* Gets the user sort direction from the session state
- *
- * @return string
*/
- protected function getSortDirection()
+ protected function getSortDirection(): string
{
- return $this->getSession('media_sort_direction', MediaLibrary::SORT_DIRECTION_ASC);
+ $key = 'media_sort_direction';
+ return $this->getSession($key, $this->getUserPreference($key, MediaLibrary::SORT_DIRECTION_ASC));
}
/**
@@ -1184,7 +1175,9 @@ protected function setViewMode(string $viewMode): void
throw new ApplicationException('Invalid input data');
}
- $this->putSession('view_mode', $viewMode);
+ $key = 'view_mode';
+ $this->putUserPreference($key, $viewMode);
+ $this->putSession($key, $viewMode);
}
/**
@@ -1192,7 +1185,8 @@ protected function setViewMode(string $viewMode): void
*/
protected function getViewMode(): string
{
- return $this->getSession('view_mode', self::VIEW_MODE_GRID);
+ $key = 'view_mode';
+ return $this->getSession($key, $this->getUserPreference($key, self::VIEW_MODE_GRID));
}
/**
@@ -1371,4 +1365,15 @@ protected function isVector(string $path): bool
{
return (pathinfo($path, PATHINFO_EXTENSION) == 'svg');
}
+
+ /**
+ * Returns a unique identifier for this widget and controller action for preference storage.
+ *
+ * @return string
+ */
+ protected function getPreferenceKey()
+ {
+ // User preferences should persist across controller usages for the MediaManager
+ return "backend::widgets.media_manager." . strtolower($this->getId());
+ }
}
diff --git a/modules/backend/widgets/lists/partials/_list_head_row.php b/modules/backend/widgets/lists/partials/_list_head_row.php
index 02f735f984..8907ca99e7 100644
--- a/modules/backend/widgets/lists/partials/_list_head_row.php
+++ b/modules/backend/widgets/lists/partials/_list_head_row.php
@@ -45,6 +45,7 @@ class="list-cell-name-= $column->getName() ?> list-cell-type-= $column->type
diff --git a/modules/backend/widgets/lists/partials/_setup_form.php b/modules/backend/widgets/lists/partials/_setup_form.php
index 7bffb53a88..b32de4412d 100644
--- a/modules/backend/widgets/lists/partials/_setup_form.php
+++ b/modules/backend/widgets/lists/partials/_setup_form.php
@@ -1,4 +1,4 @@
-= Form::open() ?>
+= Form::open(['data-request-parent' => '#' . $this->getId('setupButton')]) ?>
+
diff --git a/modules/cms/classes/AutoDatasource.php b/modules/cms/classes/AutoDatasource.php
index c554b2f9ee..f68fb1861c 100644
--- a/modules/cms/classes/AutoDatasource.php
+++ b/modules/cms/classes/AutoDatasource.php
@@ -1,13 +1,15 @@
-postProcessor = new Processor;
}
+ /**
+ * Append a datasource to the end of the list of datasources
+ */
+ public function appendDatasource(string $key, DatasourceInterface $datasource): void
+ {
+ $this->datasources[$key] = $datasource;
+ $this->pathCache[] = Cache::rememberForever($datasource->getPathsCacheKey(), function () use ($datasource) {
+ return $datasource->getAvailablePaths();
+ });
+ }
+
+ /**
+ * Prepend a datasource to the beginning of the list of datasources
+ */
+ public function prependDatasource(string $key, DatasourceInterface $datasource): void
+ {
+ $this->datasources = array_prepend($this->datasources, $datasource, $key);
+ $this->pathCache = array_prepend($this->pathCache, Cache::rememberForever($datasource->getPathsCacheKey(), function () use ($datasource) {
+ return $datasource->getAvailablePaths();
+ }), $key);
+ }
+
/**
* Returns the in memory path cache map
*/
@@ -80,9 +104,8 @@ public function getPathCache(): array
* Populate the local cache of paths available in each datasource
*
* @param boolean $refresh Default false, set to true to force the cache to be rebuilt
- * @return void
*/
- public function populateCache($refresh = false)
+ public function populateCache(bool $refresh = false): void
{
$pathCache = [];
foreach ($this->datasources as $datasource) {
@@ -108,12 +131,8 @@ public function populateCache($refresh = false)
/**
* Check to see if the specified datasource has the provided Halcyon Model
- *
- * @param string $source The string key of the datasource to check
- * @param Model $model The Halcyon Model to check for
- * @return boolean
*/
- public function sourceHasModel(string $source, Model $model)
+ public function sourceHasModel(string $source, Model $model): bool
{
if (!$model->exists) {
return false;
@@ -141,11 +160,8 @@ public function sourceHasModel(string $source, Model $model)
/**
* Get the available paths for the specified datasource key
- *
- * @param string $source The string key of the datasource to check
- * @return void
*/
- public function getSourcePaths(string $source)
+ public function getSourcePaths(string $source): array
{
$result = [];
@@ -164,11 +180,9 @@ public function getSourcePaths(string $source)
/**
* Forces all operations in a provided closure to run within a selected datasource.
*
- * @param string $source
- * @param \Closure $closure
- * @return mixed
+ * @throws ApplicationException if the provided datasource key doesn't exist
*/
- public function usingSource(string $source, \Closure $closure)
+ public function usingSource(string $source, \Closure $closure): mixed
{
if (!array_key_exists($source, $this->datasources)) {
throw new ApplicationException('Invalid datasource specified.');
@@ -191,12 +205,8 @@ public function usingSource(string $source, \Closure $closure)
/**
* Push the provided model to the specified datasource
- *
- * @param Model $model The Halcyon Model to push
- * @param string $source The string key of the datasource to use
- * @return void
*/
- public function pushToSource(Model $model, string $source)
+ public function pushToSource(Model $model, string $source): void
{
$this->usingSource($source, function () use ($model) {
$datasource = $this->getActiveDatasource();
@@ -215,12 +225,8 @@ public function pushToSource(Model $model, string $source)
/**
* Remove the provided model from the specified datasource
- *
- * @param Model $model The Halcyon model to remove
- * @param string $source The string key of the datasource to use
- * @return void
*/
- public function removeFromSource(Model $model, string $source)
+ public function removeFromSource(Model $model, string $source): void
{
$this->usingSource($source, function () use ($model) {
$datasource = $this->getActiveDatasource();
@@ -236,11 +242,8 @@ public function removeFromSource(Model $model, string $source)
/**
* Get the appropriate datasource for the provided path
- *
- * @param string $path
- * @return Datasource
*/
- protected function getDatasourceForPath(string $path)
+ protected function getDatasourceForPath(string $path): DatasourceInterface
{
// Always return the active datasource when singleDatasourceMode is enabled
if ($this->singleDatasourceMode) {
@@ -283,7 +286,7 @@ protected function getDatasourceForPath(string $path)
* ];
* @return array $paths ["$dirName/path/1.md", "$dirName/path/2.md"]
*/
- protected function getValidPaths(string $dirName, array $options = [])
+ protected function getValidPaths(string $dirName, array $options = []): array
{
// Initialize result set
$paths = [];
@@ -325,23 +328,16 @@ protected function getValidPaths(string $dirName, array $options = [])
/**
* Helper to make file path.
- *
- * @param string $dirName
- * @param string $fileName
- * @param string $extension
- * @return string
*/
- protected function makeFilePath(string $dirName, string $fileName, string $extension)
+ protected function makeFilePath(string $dirName, string $fileName, string $extension): string
{
return ltrim($dirName . '/' . $fileName . '.' . $extension, '/');
}
/**
* Get the datasource for use with CRUD operations
- *
- * @return DatasourceInterface
*/
- protected function getActiveDatasource()
+ protected function getActiveDatasource(): DatasourceInterface
{
return $this->datasources[$this->activeDatasourceKey];
}
diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php
index 74767693bd..33a516d0a4 100644
--- a/modules/cms/classes/CmsCompoundObject.php
+++ b/modules/cms/classes/CmsCompoundObject.php
@@ -294,6 +294,8 @@ public function getComponentProperties($componentName)
}
return [];
+ } else {
+ $objectComponentMap[$objectCode] = [];
}
if (!isset($this->settings['components'])) {
diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php
index c2600a505a..e1ce327278 100644
--- a/modules/cms/classes/CmsObject.php
+++ b/modules/cms/classes/CmsObject.php
@@ -138,7 +138,11 @@ public static function listInTheme($theme, $skipCache = false)
$loadedItems = [];
foreach ($items as $item) {
- $loadedItems[] = static::loadCached($theme, $item);
+ $loaded = static::loadCached($theme, $item);
+ if ($loaded) {
+ $loadedItems[] = $loaded;
+ }
+ unset($loaded);
}
$result = $instance->newCollection($loadedItems);
diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php
index 7753db63d9..dd67cc728c 100644
--- a/modules/cms/classes/Controller.php
+++ b/modules/cms/classes/Controller.php
@@ -319,6 +319,19 @@ public function runPage($page, $useAjax = true)
'session' => App::make('session'),
]);
+ /*
+ * Add global vars defined by View::share() into Twig, only if they have yet to be specified.
+ */
+ $globalVars = ViewHelper::getGlobalVars();
+ if (!empty($globalVars)) {
+ $existingGlobals = array_keys($this->getTwig()->getGlobals());
+ foreach ($globalVars as $key => $value) {
+ if (!in_array($key, $existingGlobals)) {
+ $this->getTwig()->addGlobal($key, $value);
+ }
+ }
+ }
+
/*
* Check for the presence of validation errors in the session.
*/
diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php
index 56b401dab7..bdd199ae0d 100644
--- a/modules/cms/classes/Theme.php
+++ b/modules/cms/classes/Theme.php
@@ -1,23 +1,22 @@
addMinutes(Config::get('cms.urlCacheTtl', 10));
- return Cache::remember("winter.cms.{$this->dirName}.assetUrl.$path", $expiresAt, function () use ($path) {
+ $key = sprintf('winter.cms.%s.assetUrl.%s.%s', $this->dirName, request()->getSchemeAndHttpHost(), $path);
+ return Cache::remember($key, $expiresAt, function () use ($path) {
// Handle symbolized paths
if ($path && File::isPathSymbol($path)) {
return Url::asset(File::localToPublic(File::symbolizePath($path)));
@@ -610,7 +610,7 @@ public static function databaseLayerEnabled(): bool
}
$hasDb = Cache::rememberForever('cms.databaseTemplates.hasTables', function () {
- return App::hasDatabase() && Schema::hasTable('cms_theme_templates');
+ return App::hasDatabaseTable('cms_theme_templates');
});
return $enableDbLayer && $hasDb;
diff --git a/modules/cms/console/CreateTheme.php b/modules/cms/console/CreateTheme.php
index 86f2c850a7..557337dd64 100644
--- a/modules/cms/console/CreateTheme.php
+++ b/modules/cms/console/CreateTheme.php
@@ -1,7 +1,7 @@
(eg: AuthorName.ThemeName)}
+ {dirName? : Destination directory name for the theme installation.}
+ ';
+
/**
* The console command description.
* @var string
@@ -121,16 +127,4 @@ protected function themeCodeToDir($themeCode)
{
return strtolower(str_replace('.', '-', $themeCode));
}
-
- /**
- * Get the console command arguments.
- * @return array
- */
- protected function getArguments()
- {
- return [
- ['name', InputArgument::REQUIRED, 'The name of the theme. Eg: AuthorName.ThemeName'],
- ['dirName', InputArgument::OPTIONAL, 'Destination directory name for the theme installation.'],
- ];
- }
}
diff --git a/modules/cms/console/ThemeList.php b/modules/cms/console/ThemeList.php
index 92e835c111..61b3de5063 100644
--- a/modules/cms/console/ThemeList.php
+++ b/modules/cms/console/ThemeList.php
@@ -1,10 +1,9 @@
isActiveTheme() ? '[*] ' : '[-] ';
- $themeId = $theme->getId();
- $themeName = $themeManager->findByDirName($themeId) ?: $themeId;
- $this->info($flag . $themeName);
+ $results[] = [
+ 'code' => $theme->getId(),
+ 'is_active' => $theme->isActiveTheme() ? 'Yes': 'No>',
+ 'is_installed' => 'Yes',
+ ];
}
if ($this->option('include-marketplace')) {
- // @todo List everything in the marketplace - not just popular.
-
+ // @TODO List everything in the marketplace - not just popular.
$popularThemes = $updateManager->requestPopularProducts('theme');
-
foreach ($popularThemes as $popularTheme) {
- if (!$themeManager->isInstalled($popularTheme['code'])) {
- $this->info('[ ] '.$popularTheme['code']);
- }
+ $results[] = [
+ 'code' => $popularTheme['code'],
+ 'is_active' => 'No>',
+ 'is_installed' => $themeManager->isInstalled($popularTheme['code']) ? 'Yes': 'No>',
+ ];
}
}
- $this->info(PHP_EOL."[*] Active [-] Installed [ ] Not installed");
- }
-
- /**
- * Get the console command options.
- */
- protected function getOptions()
- {
- return [
- ['include-marketplace', 'm', InputOption::VALUE_NONE, 'Include downloadable themes from the Winter marketplace.']
- ];
+ $this->table(['Theme', 'Active', 'Installed'], $results);
}
}
diff --git a/modules/cms/console/ThemeRemove.php b/modules/cms/console/ThemeRemove.php
index c583c012bb..9df51d7ac7 100644
--- a/modules/cms/console/ThemeRemove.php
+++ b/modules/cms/console/ThemeRemove.php
@@ -2,10 +2,8 @@
use Cms\Classes\Theme;
use Cms\Classes\ThemeManager;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Input\InputArgument;
-use Illuminate\Console\Command;
use Exception;
+use Winter\Storm\Console\Command;
/**
* Console command to remove a theme.
@@ -17,7 +15,6 @@
*/
class ThemeRemove extends Command
{
-
use \Illuminate\Console\ConfirmableTrait;
/**
@@ -26,6 +23,14 @@ class ThemeRemove extends Command
*/
protected $name = 'theme:remove';
+ /**
+ * @var string The name and signature of this command.
+ */
+ protected $signature = 'theme:remove
+ {name : The name of the theme to delete. (eg: mytheme)}
+ {--f|force : Force the operation to run.}
+ ';
+
/**
* The console command description.
* @var string
@@ -57,33 +62,9 @@ public function handle()
try {
$themeManager->deleteTheme($themeName);
-
$this->info(sprintf('The theme %s has been deleted.', $themeName));
- }
- catch (Exception $ex) {
+ } catch (Exception $ex) {
$this->error($ex->getMessage());
}
}
-
- /**
- * Get the console command arguments.
- * @return array
- */
- protected function getArguments()
- {
- return [
- ['name', InputArgument::REQUIRED, 'The name of the theme. (directory name)'],
- ];
- }
-
- /**
- * Get the console command options.
- * @return array
- */
- protected function getOptions()
- {
- return [
- ['force', null, InputOption::VALUE_NONE, 'Force the operation to run.'],
- ];
- }
}
diff --git a/modules/cms/console/ThemeSync.php b/modules/cms/console/ThemeSync.php
index 1b3c7375d8..1be393c001 100644
--- a/modules/cms/console/ThemeSync.php
+++ b/modules/cms/console/ThemeSync.php
@@ -1,13 +1,9 @@
laravel->hasDatabase()) {
return $this->error("The application is not using a database.");
}
@@ -224,31 +230,5 @@ protected function getModelForPath($path, $modelClass, $theme)
return $entity;
});
-
- return $entity;
- }
-
- /**
- * Get the console command arguments.
- * @return array
- */
- protected function getArguments()
- {
- return [
- ['name', InputArgument::OPTIONAL, 'The name of the theme (directory name). Defaults to currently active theme.'],
- ];
- }
-
- /**
- * Get the console command options.
- * @return array
- */
- protected function getOptions()
- {
- return [
- ['paths', null, InputOption::VALUE_REQUIRED, 'Comma-separated specific paths (relative to provided theme directory) to specificaly sync. Default is all paths. You may use regular expressions.'],
- ['target', null, InputOption::VALUE_REQUIRED, 'The target of the sync, the other will be used as the source. Defaults to "filesystem", can be "database"'],
- ['force', null, InputOption::VALUE_NONE, 'Force the operation to run.'],
- ];
}
}
diff --git a/modules/cms/console/ThemeUse.php b/modules/cms/console/ThemeUse.php
index 7cd9fa392f..17e2334193 100644
--- a/modules/cms/console/ThemeUse.php
+++ b/modules/cms/console/ThemeUse.php
@@ -1,9 +1,7 @@
'Atrast citas tēmas',
'saving' => 'Saglabā tēmu...',
'return' => 'Atgriezties tēmu sarakstā',
+ 'default_description' => 'Pielāgotā tēma, kas ģenerēta priekš :url',
+ 'scaffold' => [
+ 'label' => 'Tēmas sagatave',
+ 'empty' => 'Tukša',
+ 'less' => 'Pamata (LESS)',
+ 'tailwind' => 'Tailwind CSS',
+ ],
],
'maintenance' => [
'settings_menu' => 'Uzturēšanas režīms',
diff --git a/modules/cms/lang/ru/lang.php b/modules/cms/lang/ru/lang.php
index c10a69fc3b..c3d02830a2 100644
--- a/modules/cms/lang/ru/lang.php
+++ b/modules/cms/lang/ru/lang.php
@@ -71,12 +71,12 @@
'import_uploaded_file' => 'Файл архива темы',
'import_overwrite_label' => 'Перезаписывать существующие файлы',
'import_overwrite_comment' => 'Отключите эту опцию, чтобы импортировать только новые файлы',
- 'import_folders_label' => 'Директории',
- 'import_folders_comment' => 'Пожалуйста, выберите директории темы, которые вы хотели бы импортировать',
+ 'import_folders_label' => 'Папки',
+ 'import_folders_comment' => 'Пожалуйста, выберите папки темы, которые вы хотели бы импортировать',
'export_button' => 'Экспортировать',
'export_title' => 'Экспортировать тему',
- 'export_folders_label' => 'Директории',
- 'export_folders_comment' => 'Пожалуйста, выберите директории темы, которые вы хотели бы экспортировать',
+ 'export_folders_label' => 'Папки',
+ 'export_folders_comment' => 'Пожалуйста, выберите папки темы, которые вы хотели бы экспортировать',
'delete_button' => 'Удалить',
'delete_confirm' => 'Вы уверены, что хотите удалить эту тему? Это действие необратимо!',
'delete_active_theme_failed' => 'Невозможно удалить активный тему, попробуйте сделать другую тему активной.',
@@ -93,6 +93,13 @@
'find_more_themes' => 'Найти еще темы',
'saving' => 'Сохранение темы...',
'return' => 'Вернуться к списку тем',
+ 'default_description' => 'Пользовательская тема сгенерированая для :url',
+ 'scaffold' => [
+ 'label' => 'Каркас',
+ 'empty' => 'Пусто',
+ 'less' => 'Базовый (LESS)',
+ 'tailwind' => 'Tailwind CSS',
+ ],
],
'maintenance' => [
'settings_menu' => 'Режим обслуживания',
diff --git a/modules/cms/lang/tr/lang.php b/modules/cms/lang/tr/lang.php
index c534d0eaf4..60b4067d11 100644
--- a/modules/cms/lang/tr/lang.php
+++ b/modules/cms/lang/tr/lang.php
@@ -44,7 +44,7 @@
'description_label' => 'Açıklama',
'description_placeholder' => 'Tema açıklaması',
'homepage_label' => 'Anasayfa',
- 'homepage_placeholder' => 'Anasayfa Adresi(URL)',
+ 'homepage_placeholder' => 'Anasayfa Adresi (URL)',
'code_label' => 'Kod',
'code_placeholder' => 'Temanın dağıtımında kullanılmak üzere benzersiz bir kod.',
'preview_image_label' => 'Önizleme görseli',
@@ -93,6 +93,13 @@
'find_more_themes' => 'Daha fazla tema bulun',
'saving' => 'Tema kaydediliyor...',
'return' => 'Tema listesine geri dön',
+ 'default_description' => ':url için oluşturulan özel tema',
+ 'scaffold' => [
+ 'label' => 'Çatı',
+ 'empty' => 'Boş',
+ 'less' => 'Temel (LESS)',
+ 'tailwind' => 'Tailwind CSS',
+ ],
],
'maintenance' => [
'settings_menu' => 'Bakım modu',
@@ -100,6 +107,13 @@
'is_enabled' => 'Bakım modunu aktifleştir',
'is_enabled_comment' => 'Aktifleştirildiğinde, web sitesi ziyaretçileri aşağıdaki seçtiğiniz sayfayı görecektir.',
'hint' => 'Bakımda sayfası, yönetim paneli girişi yapmamış tüm ziyaretçilere seçilen bakımda sayfasını gösterir.',
+ 'allowed_ips' => [
+ 'name' => 'İzin verilen IP adresleri',
+ 'description' => 'Bakım modu aktifken siteyi görüntülemesine izin verilen IP adresleri',
+ 'prompt' => 'IP Adresi ekle',
+ 'ip' => 'IP adresi',
+ 'label' => 'Açıklama',
+ ],
],
'page' => [
'not_found_name' => "':name' sayfası bulunamadı",
@@ -186,6 +200,14 @@
'close_searchbox' => 'Arama kutusunu kapat',
'open_replacebox' => 'Düzenleme kutusunu aç',
'close_replacebox' => 'Düzenleme kutusunu kapat',
+ 'commit' => 'Uygula',
+ 'reset' => 'Sıfırla',
+ 'commit_confirm' => 'Bu dosyada yaptığınız değişiklikleri dosya sistemine uygulamak istediğinizden emin misiniz? Bu işlem mevcut dosyanın üzerine yazacaktır',
+ 'reset_confirm' => 'Bu dosyayı, dosya sistemindeki kopya ile resetlemek istediğinize emin misiniz? Bu, mevcut dosyayı, dosya sistemindeki ile değiştirecektir.',
+ 'committing' => 'Uygulanıyor',
+ 'resetting' => 'Sıfırlanıyor',
+ 'commit_success' => ':type dosya sisteminde düzenlendi',
+ 'reset_success' => ':type dosya sistemindeki haline sıfırlandı',
],
'asset' => [
'menu_label' => "Dosyalar",
@@ -237,7 +259,10 @@
'invalid_request' => "Şablonda hatalı bileşen verisi olduğu için kaydedilemedi.",
'no_records' => 'Bileşen bulunamadı.',
'not_found' => "':name' isimli bileşen bulunamadı.",
+ 'no_default_partial' => "Bu component için 'default' partial bulunmuyor",
'method_not_found' => "':name' isimli bileşen ':method'unu içermiyor.",
+ 'soft_component' => 'Opsiyonel Bileşen',
+ 'soft_component_description' => 'Bu bileşen eksik ancak isteğe bağlıdır.',
],
'template' => [
'invalid_type' => "Hatalı şablon tipi.",
diff --git a/modules/cms/models/ThemeLog.php b/modules/cms/models/ThemeLog.php
index 332735f2ab..6183a1862b 100644
--- a/modules/cms/models/ThemeLog.php
+++ b/modules/cms/models/ThemeLog.php
@@ -1,13 +1,11 @@
getTable())
- ) {
- return;
+ if (!LogSetting::hasDatabaseTable()) {
+ return null;
}
if (!LogSetting::get('log_theme')) {
- return;
+ return null;
}
if (!$type) {
@@ -78,7 +72,7 @@ public static function add(HalcyonModel $template, $type = null)
$oldContent = $template->getOriginal('content');
if ($newContent === $oldContent && $templateName === $oldTemplateName && !$isDelete) {
- return;
+ return null;
}
$record = new self;
@@ -95,8 +89,7 @@ public static function add(HalcyonModel $template, $type = null)
try {
$record->save();
- }
- catch (Exception $ex) {
+ } catch (Exception $ex) {
}
return $record;
diff --git a/modules/cms/tests/classes/CmsObjectQueryTest.php b/modules/cms/tests/classes/CmsObjectQueryTest.php
index 73c06939bb..4f1ca68947 100644
--- a/modules/cms/tests/classes/CmsObjectQueryTest.php
+++ b/modules/cms/tests/classes/CmsObjectQueryTest.php
@@ -84,6 +84,8 @@ public function testLists()
"no-soft-component-class",
"optional-full-php-tags",
"optional-short-php-tags",
+ "shared-variable",
+ "shared-variable-override",
"throw-php",
"with-component",
"with-components",
diff --git a/modules/cms/tests/classes/ControllerTest.php b/modules/cms/tests/classes/ControllerTest.php
index 8899ef5c54..16d72cb9ce 100644
--- a/modules/cms/tests/classes/ControllerTest.php
+++ b/modules/cms/tests/classes/ControllerTest.php
@@ -3,10 +3,11 @@
namespace Cms\Tests\Classes;
use Cms;
+use Request;
use System\Tests\Bootstrap\TestCase;
use Cms\Classes\Theme;
use Cms\Classes\Controller;
-use Request;
+use System\Helpers\View;
use Winter\Storm\Halcyon\Model;
use Winter\Storm\Support\Facades\Config;
@@ -25,6 +26,8 @@ public function setUp(): void
Model::clearBootedModels();
Model::flushEventListeners();
+ View::clearVarCache();
+
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/tester/components/Archive.php';
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/tester/components/Post.php';
include_once base_path() . '/modules/system/tests/fixtures/plugins/winter/tester/components/MainMenu.php';
@@ -682,4 +685,68 @@ public function testMacro()
$response
);
}
+
+ public function testSharedVariable()
+ {
+ $this->app['view']->share('winterStatus', 'Is Awesome');
+
+ $theme = Theme::load('test');
+ $controller = new Controller($theme);
+ $response = $controller->run('/shared-variable')->getContent();
+
+ $this->assertStringContainsString(
+ ' Winter Is Awesome ',
+ $response
+ );
+
+ $this->assertStringContainsString(
+ '/shared-variable ',
+ $response
+ );
+ }
+
+ public function testSharedVariableCannotOverrideSystemGlobals()
+ {
+ $this->app['view']->share('winterStatus', 'Is Awesome');
+
+ // This override should not apply and change the page URL in the fixture template
+ $this->app['view']->share('this', [
+ 'page' => [
+ 'url' => '/overriden',
+ ],
+ ]);
+
+ $theme = Theme::load('test');
+ $controller = new Controller($theme);
+ $response = $controller->run('/shared-variable')->getContent();
+
+ $this->assertStringContainsString(
+ 'Winter Is Awesome ',
+ $response
+ );
+
+ $this->assertStringContainsString(
+ '/shared-variable ',
+ $response
+ );
+ }
+
+ public function testSharedVariableCanBeOverriddenLocally()
+ {
+ $this->app['view']->share('winterStatus', 'Is Awesome');
+
+ $theme = Theme::load('test');
+ $controller = new Controller($theme);
+ $response = $controller->run('/shared-variable-override')->getContent();
+
+ $this->assertStringContainsString(
+ 'Winter Is Coming ',
+ $response
+ );
+
+ $this->assertStringContainsString(
+ '/shared-variable-override ',
+ $response
+ );
+ }
}
diff --git a/modules/cms/tests/fixtures/themes/test/pages/shared-variable-override.htm b/modules/cms/tests/fixtures/themes/test/pages/shared-variable-override.htm
new file mode 100644
index 0000000000..38c692f09c
--- /dev/null
+++ b/modules/cms/tests/fixtures/themes/test/pages/shared-variable-override.htm
@@ -0,0 +1,10 @@
+url = '/shared-variable-override'
+==
+Winter {{ winterStatus }}
+
+{{ this.page.url }}
diff --git a/modules/cms/tests/fixtures/themes/test/pages/shared-variable.htm b/modules/cms/tests/fixtures/themes/test/pages/shared-variable.htm
new file mode 100644
index 0000000000..2bad763902
--- /dev/null
+++ b/modules/cms/tests/fixtures/themes/test/pages/shared-variable.htm
@@ -0,0 +1,5 @@
+url = '/shared-variable'
+==
+Winter {{ winterStatus }}
+
+{{ this.page.url }}
diff --git a/modules/cms/twig/SnowboardNode.php b/modules/cms/twig/SnowboardNode.php
index 2acf40a026..0471f9d6ab 100644
--- a/modules/cms/twig/SnowboardNode.php
+++ b/modules/cms/twig/SnowboardNode.php
@@ -1,12 +1,11 @@
write("\$_minify = ".CombineAssets::class."::instance()->useMinify;" . PHP_EOL);
$moduleMap = [
- 'base' => (Config::get('develop.debugSnowboard', Config::get('app.debug', false)) === true)
+ 'base' => (Config::get('develop.debugSnowboard', false) === true)
? 'snowboard.base.debug'
: 'snowboard.base',
'vendor' => 'snowboard.vendor',
@@ -56,16 +55,17 @@ public function compile(TwigCompiler $compiler)
if (!static::$baseLoaded) {
// Add manifest and vendor files
$compiler
- ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
$vendorJs = $moduleMap['vendor'];
$compiler
- ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
// Add base script
$baseJs = $moduleMap['base'];
$baseUrl = Url::to('/');
+ $assetUrl = Url::asset('/');
$compiler
- ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
static::$baseLoaded = true;
}
@@ -73,7 +73,7 @@ public function compile(TwigCompiler $compiler)
foreach ($modules as $module) {
$moduleJs = $moduleMap[$module];
$compiler
- ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
}
}
}
diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php
index 2dc967b27e..f87347bdcd 100644
--- a/modules/system/ServiceProvider.php
+++ b/modules/system/ServiceProvider.php
@@ -147,9 +147,7 @@ protected function registerPrivilegedActions()
$requests = ['/combine/', '@/system/updates', '@/system/install', '@/backend/auth'];
$commands = ['migrate', 'winter:up', 'winter:update', 'winter:env', 'winter:version', 'winter:manifest'];
- /*
- * Requests
- */
+ // Requests
$path = RouterHelper::normalizeUrl(Request::path());
$backendUri = RouterHelper::normalizeUrl(Config::get('cms.backendUri', 'backend'));
foreach ($requests as $request) {
@@ -162,10 +160,20 @@ protected function registerPrivilegedActions()
}
}
- /*
- * CLI
- */
- if ($this->app->runningInConsole() && count(array_intersect($commands, Request::server('argv', []))) > 0) {
+ // CLI
+ if ($this->app->runningInConsole()
+ && (
+ // Protected command
+ count(array_intersect($commands, Request::server('argv', []))) > 0
+
+ // Database configured but not initialized yet
+ // @see octobercms/october#3208
+ || (
+ $this->app->hasDatabase()
+ && !Schema::hasTable(UpdateManager::instance()->getMigrationTableName())
+ )
+ )
+ ) {
PluginManager::$noInit = true;
}
}
@@ -228,11 +236,6 @@ protected function registerConsole()
* Allow plugins to use the scheduler
*/
Event::listen('console.schedule', function ($schedule) {
- // Fix initial system migration with plugins that use settings for scheduling - see #3208
- if ($this->app->hasDatabase() && !Schema::hasTable(UpdateManager::instance()->getMigrationTableName())) {
- return;
- }
-
$plugins = PluginManager::instance()->getPlugins();
foreach ($plugins as $plugin) {
if (method_exists($plugin, 'registerSchedule')) {
@@ -257,6 +260,7 @@ protected function registerConsole()
$this->registerConsoleCommand('create.model', \System\Console\CreateModel::class);
$this->registerConsoleCommand('create.plugin', \System\Console\CreatePlugin::class);
$this->registerConsoleCommand('create.settings', \System\Console\CreateSettings::class);
+ $this->registerConsoleCommand('create.test', \System\Console\CreateTest::class);
$this->registerConsoleCommand('winter.up', \System\Console\WinterUp::class);
$this->registerConsoleCommand('winter.down', \System\Console\WinterDown::class);
diff --git a/modules/system/assets/js/build/system.debug.js b/modules/system/assets/js/build/system.debug.js
index 6295880f04..00e4106c93 100644
--- a/modules/system/assets/js/build/system.debug.js
+++ b/modules/system/assets/js/build/system.debug.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[714,450,988],{579:function(e,t,s){s.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ajaxLoadAssets:"load"}}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{if(document.querySelector(`script[src="${e}"]`))return void t();const n=document.createElement("script");n.setAttribute("type","text/javascript"),n.setAttribute("src",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,n),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(n)}))}loadStyle(e){return new Promise(((t,s)=>{if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const n=document.createElement("link");n.setAttribute("rel","stylesheet"),n.setAttribute("href",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,n),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(n)}))}loadImage(e){return new Promise(((t,s)=>{const n=new Image;n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,n),s(new Error(`Unable to load image file: "${e}"`))})),n.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(e instanceof n.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e){this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()}))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().to("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s,n,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=n?this.parseDuration(n):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,n]=e;-1!==t.indexOf(s)&&i.push(n)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},662:function(e,t){t.Z={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}}},286:function(e,t,s){s.d(t,{Z:function(){return g}});var n=s(579),i=s(281),r={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,s){this.name=e,this.snowboard=new Proxy(t,r),this.instance=s,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),s=0;s!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...s),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instances[0][t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instances[0][s]=function(){for(var t=arguments.length,s=new Array(t),i=0;i0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instance.prototype[t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instance.prototype[s]=function(){for(var t=arguments.length,s=new Array(t),i=0;ithis.instances.splice(this.instances.indexOf(i),1),i.construct(...s),this.instances.push(i),i}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof n.Z==!1}isSingleton(){return this.instance.prototype instanceof i.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),s=0;sthis.instances.splice(this.instances.indexOf(n),1),n.construct(...t),this.instances.push(n),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var s=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,n=new Array(e),i=0;i{const[t,s]=e;void 0!==this.defaults[t]&&(this.defaults[t]=s)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[s,n]=t;null!==this.defaults[s]&&(e[s]=n)})),e}get(e){if(void 0===e){const e=a.Z.get();return Object.entries(e).forEach((t=>{const[s,n]=t;this.snowboard.globalEvent("cookie.get",s,n,(t=>{e[s]=t}))})),e}let t=a.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,s){let n=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{n=e})),a.Z.set(e,n,h(h({},this.getDefaults()),s))}remove(e,t){a.Z.remove(e,h(h({},this.getDefaults()),t))}}class u extends i.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let s="",n=null,i=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){s="";for(let n=t;n="0"&&e[n]<="9"))return{originLength:s.length,body:s};s+=e[n]}throw new Error(`Broken JSON number body near ${s}`)}if("{"===e[t]||"["===e[t]){const n=[e[t]];s=e[t];for(let i=t+1;i=0?t-5:0,50)}`)}parseKey(e,t,s){let n="";for(let i=t;i="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class f extends i.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const s=(new DOMParser).parseFromString(e,"text/html"),n=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(s.getRootNode()),n?s.body.innerHTML:s.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const s=e.toLowerCase();if(this.hasPlugin(s))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof n.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[s])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[s]=new o(s,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((s=>{const[n,i]=s;if(i.isFunction())return;if(!i.dependenciesFulfilled())return;if(!i.hasMethod("listens"))return;const r=i.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(n)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const s=this.listeners[e].indexOf(t);-1!==s&&this.listeners[e].splice(s,1)}globalEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if(!r)if("function"==typeof i)try{!1===i.apply(n,s)&&(r=!0)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{!1===n[i](...s)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...s)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if("function"==typeof i)try{const e=i.apply(n,s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{const e=n[i](...s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...s);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,s){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,s);for(var n=arguments.length,i=new Array(n>3?n-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1?t-1:0),n=1;n1?t-1:0),n=1;n{const t=new Proxy(new n.Z(!0,!0),i.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)},640:function(e,t,s){var n=s(579);function i(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,n)}return s}function r(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(r(r({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void s();const n={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(n[t]=s)})),0===Object.keys(n).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(n).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{s()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,n]=e;let i=this.options.update&&this.options.update[t]?this.options.update[t]:t,r="replace";"@"===i.substr(0,1)?(r="append",i=i.substr(1)):"^"===i.substr(0,1)?(r="prepend",i=i.substr(1)):"#"!==i.substr(0,1)&&"."!==i.substr(0,1)&&(r="noop");const o=document.querySelectorAll(i);o.length>0&&o.forEach((e=>{switch(r){case"append":e.innerHTML+=n;break;case"prepend":e.innerHTML=n+e.innerHTML;break;case"noop":break;default:e.innerHTML=n}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,n,this);const t=new Event("ajaxUpdate");t.content=n,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,n]=e;t.append(s,n)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,n,i){const r=new Error(e);return r.exception=t||null,r.file=s||null,r.line=n||null,r.trace=i||[],r}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t=function(t){return e(e.s=t)};e.O(0,[109],(function(){return t(165),t(640),t(136)}));e.O()}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[714,450,988],{579:function(e,t,s){s.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ajaxLoadAssets:"load"}}dependencies(){return["url"]}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`script[src="${e}"]`))return void t();const n=document.createElement("script");n.setAttribute("type","text/javascript"),n.setAttribute("src",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,n),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(n)}))}loadStyle(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const n=document.createElement("link");n.setAttribute("rel","stylesheet"),n.setAttribute("href",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,n),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(n)}))}loadImage(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);const n=new Image;n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,n),s(new Error(`Unable to load image file: "${e}"`))})),n.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(e instanceof n.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e,t){!1!==t.options.stripe&&(this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()})))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().asset("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s,n,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=n?this.parseDuration(n):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,n]=e;-1!==t.indexOf(s)&&i.push(n)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},662:function(e,t){t.Z={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}}},286:function(e,t,s){s.d(t,{Z:function(){return g}});var n=s(579),i=s(281),r={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,s){this.name=e,this.snowboard=new Proxy(t,r),this.instance=s,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),s=0;s!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...s),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instances[0][t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instances[0][s]=function(){for(var t=arguments.length,s=new Array(t),i=0;i0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instance.prototype[t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instance.prototype[s]=function(){for(var t=arguments.length,s=new Array(t),i=0;ithis.instances.splice(this.instances.indexOf(i),1),i.construct(...s),this.instances.push(i),i}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof n.Z==!1}isSingleton(){return this.instance.prototype instanceof i.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),s=0;sthis.instances.splice(this.instances.indexOf(n),1),n.construct(...t),this.instances.push(n),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var s=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,n=new Array(e),i=0;i{const[t,s]=e;void 0!==this.defaults[t]&&(this.defaults[t]=s)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[s,n]=t;null!==this.defaults[s]&&(e[s]=n)})),e}get(e){if(void 0===e){const e=a.Z.get();return Object.entries(e).forEach((t=>{const[s,n]=t;this.snowboard.globalEvent("cookie.get",s,n,(t=>{e[s]=t}))})),e}let t=a.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,s){let n=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{n=e})),a.Z.set(e,n,h(h({},this.getDefaults()),s))}remove(e,t){a.Z.remove(e,h(h({},this.getDefaults()),t))}}class u extends i.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let s="",n=null,i=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){s="";for(let n=t;n="0"&&e[n]<="9"))return{originLength:s.length,body:s};s+=e[n]}throw new Error(`Broken JSON number body near ${s}`)}if("{"===e[t]||"["===e[t]){const n=[e[t]];s=e[t];for(let i=t+1;i=0?t-5:0,50)}`)}parseKey(e,t,s){let n="";for(let i=t;i="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class f extends i.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const s=(new DOMParser).parseFromString(e,"text/html"),n=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(s.getRootNode()),n?s.body.innerHTML:s.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const s=e.toLowerCase();if(this.hasPlugin(s))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof n.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[s])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[s]=new o(s,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((s=>{const[n,i]=s;if(i.isFunction())return;if(!i.dependenciesFulfilled())return;if(!i.hasMethod("listens"))return;const r=i.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(n)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const s=this.listeners[e].indexOf(t);-1!==s&&this.listeners[e].splice(s,1)}globalEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if(!r)if("function"==typeof i)try{!1===i.apply(n,s)&&(r=!0)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{!1===n[i](...s)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...s)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if("function"==typeof i)try{const e=i.apply(n,s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{const e=n[i](...s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...s);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,s){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,s);for(var n=arguments.length,i=new Array(n>3?n-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1?t-1:0),n=1;n1?t-1:0),n=1;n{const t=new Proxy(new n.Z(!0,!0),i.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)},640:function(e,t,s){var n=s(579);function i(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,n)}return s}function r(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(r(r({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void t();const n={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(n[t]=s)})),0===Object.keys(n).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(n).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{t()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,n]=e;let i=this.options.update&&this.options.update[t]?this.options.update[t]:t,r="replace";"@"===i.substr(0,1)?(r="append",i=i.substr(1)):"^"===i.substr(0,1)?(r="prepend",i=i.substr(1)):"#"!==i.substr(0,1)&&"."!==i.substr(0,1)&&(r="noop");const o=document.querySelectorAll(i);o.length>0&&o.forEach((e=>{switch(r){case"append":e.innerHTML+=n;break;case"prepend":e.innerHTML=n+e.innerHTML;break;case"noop":break;default:e.innerHTML=n}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,n,this);const t=new Event("ajaxUpdate");t.content=n,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,n]=e;t.append(s,n)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,n,i){const r=new Error(e);return r.exception=t||null,r.file=s||null,r.line=n||null,r.trace=i||[],r}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t=function(t){return e(e.s=t)};e.O(0,[109],(function(){return t(165),t(640),t(136)}));e.O()}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/build/system.js b/modules/system/assets/js/build/system.js
index 2ec7a07bb2..a60492e24d 100644
--- a/modules/system/assets/js/build/system.js
+++ b/modules/system/assets/js/build/system.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[290,243,988],{579:function(e,t,s){s.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ajaxLoadAssets:"load"}}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{if(document.querySelector(`script[src="${e}"]`))return void t();const n=document.createElement("script");n.setAttribute("type","text/javascript"),n.setAttribute("src",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,n),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(n)}))}loadStyle(e){return new Promise(((t,s)=>{if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const n=document.createElement("link");n.setAttribute("rel","stylesheet"),n.setAttribute("href",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,n),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(n)}))}loadImage(e){return new Promise(((t,s)=>{const n=new Image;n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,n),s(new Error(`Unable to load image file: "${e}"`))})),n.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(e instanceof n.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e){this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()}))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().to("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s,n,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=n?this.parseDuration(n):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,n]=e;-1!==t.indexOf(s)&&i.push(n)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},662:function(e,t){t.Z={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}}},286:function(e,t,s){s.d(t,{Z:function(){return g}});var n=s(579),i=s(281),r={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,s){this.name=e,this.snowboard=new Proxy(t,r),this.instance=s,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),s=0;s!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...s),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instances[0][t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instances[0][s]=function(){for(var t=arguments.length,s=new Array(t),i=0;i0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instance.prototype[t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instance.prototype[s]=function(){for(var t=arguments.length,s=new Array(t),i=0;ithis.instances.splice(this.instances.indexOf(i),1),i.construct(...s),this.instances.push(i),i}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof n.Z==!1}isSingleton(){return this.instance.prototype instanceof i.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),s=0;sthis.instances.splice(this.instances.indexOf(n),1),n.construct(...t),this.instances.push(n),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var s=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,n=new Array(e),i=0;i{const[t,s]=e;void 0!==this.defaults[t]&&(this.defaults[t]=s)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[s,n]=t;null!==this.defaults[s]&&(e[s]=n)})),e}get(e){if(void 0===e){const e=a.Z.get();return Object.entries(e).forEach((t=>{const[s,n]=t;this.snowboard.globalEvent("cookie.get",s,n,(t=>{e[s]=t}))})),e}let t=a.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,s){let n=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{n=e})),a.Z.set(e,n,h(h({},this.getDefaults()),s))}remove(e,t){a.Z.remove(e,h(h({},this.getDefaults()),t))}}class u extends i.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let s="",n=null,i=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){s="";for(let n=t;n="0"&&e[n]<="9"))return{originLength:s.length,body:s};s+=e[n]}throw new Error(`Broken JSON number body near ${s}`)}if("{"===e[t]||"["===e[t]){const n=[e[t]];s=e[t];for(let i=t+1;i=0?t-5:0,50)}`)}parseKey(e,t,s){let n="";for(let i=t;i="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class f extends i.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const s=(new DOMParser).parseFromString(e,"text/html"),n=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(s.getRootNode()),n?s.body.innerHTML:s.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const s=e.toLowerCase();if(this.hasPlugin(s))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof n.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[s])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[s]=new o(s,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((s=>{const[n,i]=s;if(i.isFunction())return;if(!i.dependenciesFulfilled())return;if(!i.hasMethod("listens"))return;const r=i.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(n)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const s=this.listeners[e].indexOf(t);-1!==s&&this.listeners[e].splice(s,1)}globalEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if(!r)if("function"==typeof i)try{!1===i.apply(n,s)&&(r=!0)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{!1===n[i](...s)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...s)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if("function"==typeof i)try{const e=i.apply(n,s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{const e=n[i](...s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...s);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,s){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,s);for(var n=arguments.length,i=new Array(n>3?n-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1?t-1:0),n=1;n1?t-1:0),n=1;n{const t=new Proxy(new n.Z,i.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)},640:function(e,t,s){var n=s(579);function i(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,n)}return s}function r(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(r(r({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void s();const n={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(n[t]=s)})),0===Object.keys(n).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(n).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{s()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,n]=e;let i=this.options.update&&this.options.update[t]?this.options.update[t]:t,r="replace";"@"===i.substr(0,1)?(r="append",i=i.substr(1)):"^"===i.substr(0,1)?(r="prepend",i=i.substr(1)):"#"!==i.substr(0,1)&&"."!==i.substr(0,1)&&(r="noop");const o=document.querySelectorAll(i);o.length>0&&o.forEach((e=>{switch(r){case"append":e.innerHTML+=n;break;case"prepend":e.innerHTML=n+e.innerHTML;break;case"noop":break;default:e.innerHTML=n}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,n,this);const t=new Event("ajaxUpdate");t.content=n,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,n]=e;t.append(s,n)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,n,i){const r=new Error(e);return r.exception=t||null,r.file=s||null,r.line=n||null,r.trace=i||[],r}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t=function(t){return e(e.s=t)};e.O(0,[109],(function(){return t(236),t(640),t(136)}));e.O()}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[290,243,988],{579:function(e,t,s){s.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ajaxLoadAssets:"load"}}dependencies(){return["url"]}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`script[src="${e}"]`))return void t();const n=document.createElement("script");n.setAttribute("type","text/javascript"),n.setAttribute("src",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,n),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(n)}))}loadStyle(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const n=document.createElement("link");n.setAttribute("rel","stylesheet"),n.setAttribute("href",e),n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,n),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(n)}))}loadImage(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);const n=new Image;n.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,n),t()})),n.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,n),s(new Error(`Unable to load image file: "${e}"`))})),n.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(e instanceof n.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e,t){!1!==t.options.stripe&&(this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()})))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(281);class i extends n.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().asset("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var n=s(579);class i extends n.Z{construct(e,t,s,n,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=n?this.parseDuration(n):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,n]=e;-1!==t.indexOf(s)&&i.push(n)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},662:function(e,t){t.Z={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}}},286:function(e,t,s){s.d(t,{Z:function(){return g}});var n=s(579),i=s(281),r={get(e,t,s){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(s))return function(){return Reflect.get(e,"plugins")[s].getInstance(...arguments)}}return Reflect.get(e,t,s)},has(e,t){if("string"==typeof t){const s=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(s))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,s){this.name=e,this.snowboard=new Proxy(t,r),this.instance=s,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),s=0;s!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...s),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instances[0][t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instances[0][s]=function(){for(var t=arguments.length,s=new Array(t),i=0;i0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,s]=e;this.instance.prototype[t]=s})),Object.entries(this.mocks).forEach((t=>{const[s,n]=t;this.instance.prototype[s]=function(){for(var t=arguments.length,s=new Array(t),i=0;ithis.instances.splice(this.instances.indexOf(i),1),i.construct(...s),this.instances.push(i),i}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof n.Z==!1}isSingleton(){return this.instance.prototype instanceof i.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),s=0;sthis.instances.splice(this.instances.indexOf(n),1),n.construct(...t),this.instances.push(n),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var s=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,n=new Array(e),i=0;i{const[t,s]=e;void 0!==this.defaults[t]&&(this.defaults[t]=s)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[s,n]=t;null!==this.defaults[s]&&(e[s]=n)})),e}get(e){if(void 0===e){const e=a.Z.get();return Object.entries(e).forEach((t=>{const[s,n]=t;this.snowboard.globalEvent("cookie.get",s,n,(t=>{e[s]=t}))})),e}let t=a.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,s){let n=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{n=e})),a.Z.set(e,n,h(h({},this.getDefaults()),s))}remove(e,t){a.Z.remove(e,h(h({},this.getDefaults()),t))}}class u extends i.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let s="",n=null,i=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){s="";for(let n=t;n="0"&&e[n]<="9"))return{originLength:s.length,body:s};s+=e[n]}throw new Error(`Broken JSON number body near ${s}`)}if("{"===e[t]||"["===e[t]){const n=[e[t]];s=e[t];for(let i=t+1;i=0?t-5:0,50)}`)}parseKey(e,t,s){let n="";for(let i=t;i="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class f extends i.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const s=(new DOMParser).parseFromString(e,"text/html"),n=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(s.getRootNode()),n?s.body.innerHTML:s.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const s=e.toLowerCase();if(this.hasPlugin(s))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof n.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[s])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[s]=new o(s,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((s=>{const[n,i]=s;if(i.isFunction())return;if(!i.dependenciesFulfilled())return;if(!i.hasMethod("listens"))return;const r=i.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(n)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const s=this.listeners[e].indexOf(t);-1!==s&&this.listeners[e].splice(s,1)}globalEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if(!r)if("function"==typeof i)try{!1===i.apply(n,s)&&(r=!0)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{!1===n[i](...s)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...s)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n{const n=this.getPlugin(t);if(n.isFunction())return;n.isSingleton()&&0===n.getInstances().length&&n.initialiseSingleton();const i=n.callMethod("listens")[e];n.getInstances().forEach((n=>{if("function"==typeof i)try{const e=i.apply(n,s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,s)}else if("string"==typeof i){if(!n[i])throw new Error(`Missing "${i}" method in "${t}" plugin`);try{const e=n[i](...s);if(e instanceof Promise==!1)return;r.push(e)}catch(s){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,s)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...s);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,s){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,s);for(var n=arguments.length,i=new Array(n>3?n-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1?t-1:0),n=1;n1?t-1:0),n=1;n{const t=new Proxy(new n.Z,i.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)},640:function(e,t,s){var n=s(579);function i(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,n)}return s}function r(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(r(r({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void t();const n={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(n[t]=s)})),0===Object.keys(n).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(n).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{t()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,n]=e;let i=this.options.update&&this.options.update[t]?this.options.update[t]:t,r="replace";"@"===i.substr(0,1)?(r="append",i=i.substr(1)):"^"===i.substr(0,1)?(r="prepend",i=i.substr(1)):"#"!==i.substr(0,1)&&"."!==i.substr(0,1)&&(r="noop");const o=document.querySelectorAll(i);o.length>0&&o.forEach((e=>{switch(r){case"append":e.innerHTML+=n;break;case"prepend":e.innerHTML=n+e.innerHTML;break;case"noop":break;default:e.innerHTML=n}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,n,this);const t=new Event("ajaxUpdate");t.content=n,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,n]=e;t.append(s,n)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,n,i){const r=new Error(e);return r.exception=t||null,r.file=s||null,r.line=n||null,r.trace=i||[],r}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t=function(t){return e(e.s=t)};e.O(0,[109],(function(){return t(236),t(640),t(136)}));e.O()}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/ajax/Request.js b/modules/system/assets/js/snowboard/ajax/Request.js
index 1f5670634f..b098719047 100644
--- a/modules/system/assets/js/snowboard/ajax/Request.js
+++ b/modules/system/assets/js/snowboard/ajax/Request.js
@@ -301,7 +301,7 @@ export default class Request extends PluginBase {
return new Promise((resolve, reject) => {
if (typeof this.options.beforeUpdate === 'function') {
if (this.options.beforeUpdate.apply(this, [response]) === false) {
- reject();
+ resolve();
return;
}
}
@@ -350,7 +350,7 @@ export default class Request extends PluginBase {
);
},
() => {
- reject();
+ resolve();
},
);
});
diff --git a/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js b/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js
index 24e01a64fc..eebe1a2585 100644
--- a/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js
+++ b/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js
@@ -111,7 +111,7 @@ export default class AttributeRequest extends Singleton {
clickHandler(event) {
let currentElement = event.target;
- while (currentElement.tagName !== 'HTML') {
+ while (currentElement && currentElement.tagName !== 'HTML') {
if (!currentElement.matches(
'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]',
)) {
diff --git a/modules/system/assets/js/snowboard/build/snowboard.base.debug.js b/modules/system/assets/js/snowboard/build/snowboard.base.debug.js
index b8211d7999..513538bfdf 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.base.debug.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.base.debug.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[450],{579:function(e,t,n){n.d(t,{Z:function(){return i}});class i{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,n){n.d(t,{Z:function(){return r}});var i=n(579);class r extends i.Z{}},662:function(e,t){t.Z={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}}},286:function(e,t,n){n.d(t,{Z:function(){return b}});var i=n(579),r=n(281),s={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,n){this.name=e,this.snowboard=new Proxy(t,s),this.instance=n,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),n=0;n!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...n),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instances[0][t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instances[0][n]=function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instance.prototype[t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instance.prototype[n]=function(){for(var t=arguments.length,n=new Array(t),r=0;rthis.instances.splice(this.instances.indexOf(r),1),r.construct(...n),this.instances.push(r),r}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof i.Z==!1}isSingleton(){return this.instance.prototype instanceof r.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),n=0;nthis.instances.splice(this.instances.indexOf(i),1),i.construct(...t),this.instances.push(i),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var n=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,i=new Array(e),r=0;r{const[t,n]=e;void 0!==this.defaults[t]&&(this.defaults[t]=n)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[n,i]=t;null!==this.defaults[n]&&(e[n]=i)})),e}get(e){if(void 0===e){const e=l.Z.get();return Object.entries(e).forEach((t=>{const[n,i]=t;this.snowboard.globalEvent("cookie.get",n,i,(t=>{e[n]=t}))})),e}let t=l.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,n){let i=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{i=e})),l.Z.set(e,i,h(h({},this.getDefaults()),n))}remove(e,t){l.Z.remove(e,h(h({},this.getDefaults()),t))}}class f extends r.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let n="",i=null,r=null,s="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");s='"';for(let e=1;e="0"&&e[t]<="9"){n="";for(let i=t;i="0"&&e[i]<="9"))return{originLength:n.length,body:n};n+=e[i]}throw new Error(`Broken JSON number body near ${n}`)}if("{"===e[t]||"["===e[t]){const i=[e[t]];n=e[t];for(let r=t+1;r=0?t-5:0,50)}`)}parseKey(e,t,n){let i="";for(let r=t;r="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class g extends r.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const n=(new DOMParser).parseFromString(e,"text/html"),i=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(n.getRootNode()),i?n.body.innerHTML:n.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const n=e.toLowerCase();if(this.hasPlugin(n))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof i.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[n])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[n]=new o(n,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((n=>{const[i,r]=n;if(r.isFunction())return;if(!r.dependenciesFulfilled())return;if(!r.hasMethod("listens"))return;const s=r.callMethod("listens");"string"!=typeof s[e]&&"function"!=typeof s[e]||t.push(i)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const n=this.listeners[e].indexOf(t);-1!==n&&this.listeners[e].splice(n,1)}globalEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const r=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if(!s)if("function"==typeof r)try{!1===r.apply(i,n)&&(s=!0)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof r){if(!i[r])throw new Error(`Missing "${r}" method in "${t}" plugin`);try{!1===i[r](...n)&&(s=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!s&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!s)try{!1===t(...n)&&(s=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!s}globalPromiseEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const r=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if("function"==typeof r)try{const e=r.apply(i,n);if(e instanceof Promise==!1)return;s.push(e)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof r){if(!i[r])throw new Error(`Missing "${r}" method in "${t}" plugin`);try{const e=i[r](...n);if(e instanceof Promise==!1)return;s.push(e)}catch(n){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...n);if(e instanceof Promise==!1)return;s.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===s.length?Promise.resolve():Promise.all(s)}logMessage(e,t,n){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,n);for(var i=arguments.length,r=new Array(i>3?i-3:0),s=3;s{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i1?t-1:0),i=1;i1?t-1:0),i=1;i{const t=new Proxy(new i.Z(!0,!0),r.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)}},function(e){e.O(0,[109],(function(){return t=165,e(e.s=t);var t}));e.O()}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[450],{579:function(e,t,n){n.d(t,{Z:function(){return i}});class i{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,n){n.d(t,{Z:function(){return s}});var i=n(579);class s extends i.Z{}},662:function(e,t){t.Z={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}}},286:function(e,t,n){n.d(t,{Z:function(){return b}});var i=n(579),s=n(281),r={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,n){this.name=e,this.snowboard=new Proxy(t,r),this.instance=n,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),n=0;n!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...n),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instances[0][t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instances[0][n]=function(){for(var t=arguments.length,n=new Array(t),s=0;s0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instance.prototype[t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instance.prototype[n]=function(){for(var t=arguments.length,n=new Array(t),s=0;sthis.instances.splice(this.instances.indexOf(s),1),s.construct(...n),this.instances.push(s),s}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof i.Z==!1}isSingleton(){return this.instance.prototype instanceof s.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),n=0;nthis.instances.splice(this.instances.indexOf(i),1),i.construct(...t),this.instances.push(i),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var n=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,i=new Array(e),s=0;s{const[t,n]=e;void 0!==this.defaults[t]&&(this.defaults[t]=n)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[n,i]=t;null!==this.defaults[n]&&(e[n]=i)})),e}get(e){if(void 0===e){const e=l.Z.get();return Object.entries(e).forEach((t=>{const[n,i]=t;this.snowboard.globalEvent("cookie.get",n,i,(t=>{e[n]=t}))})),e}let t=l.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,n){let i=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{i=e})),l.Z.set(e,i,h(h({},this.getDefaults()),n))}remove(e,t){l.Z.remove(e,h(h({},this.getDefaults()),t))}}class f extends s.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let n="",i=null,s=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){n="";for(let i=t;i="0"&&e[i]<="9"))return{originLength:n.length,body:n};n+=e[i]}throw new Error(`Broken JSON number body near ${n}`)}if("{"===e[t]||"["===e[t]){const i=[e[t]];n=e[t];for(let s=t+1;s=0?t-5:0,50)}`)}parseKey(e,t,n){let i="";for(let s=t;s="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class d extends s.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const n=(new DOMParser).parseFromString(e,"text/html"),i=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(n.getRootNode()),i?n.body.innerHTML:n.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const n=e.toLowerCase();if(this.hasPlugin(n))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof i.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[n])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[n]=new o(n,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((n=>{const[i,s]=n;if(s.isFunction())return;if(!s.dependenciesFulfilled())return;if(!s.hasMethod("listens"))return;const r=s.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(i)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const n=this.listeners[e].indexOf(t);-1!==n&&this.listeners[e].splice(n,1)}globalEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const s=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if(!r)if("function"==typeof s)try{!1===s.apply(i,n)&&(r=!0)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof s){if(!i[s])throw new Error(`Missing "${s}" method in "${t}" plugin`);try{!1===i[s](...n)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...n)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const s=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if("function"==typeof s)try{const e=s.apply(i,n);if(e instanceof Promise==!1)return;r.push(e)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof s){if(!i[s])throw new Error(`Missing "${s}" method in "${t}" plugin`);try{const e=i[s](...n);if(e instanceof Promise==!1)return;r.push(e)}catch(n){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...n);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,n){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,n);for(var i=arguments.length,s=new Array(i>3?i-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i1?t-1:0),i=1;i1?t-1:0),i=1;i{const t=new Proxy(new i.Z(!0,!0),s.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)}},function(e){e.O(0,[109],(function(){return t=165,e(e.s=t);var t}));e.O()}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/build/snowboard.base.js b/modules/system/assets/js/snowboard/build/snowboard.base.js
index a94056e5cb..e92fb327a1 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.base.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.base.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[243],{579:function(e,t,n){n.d(t,{Z:function(){return i}});class i{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,n){n.d(t,{Z:function(){return r}});var i=n(579);class r extends i.Z{}},662:function(e,t){t.Z={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}}},286:function(e,t,n){n.d(t,{Z:function(){return b}});var i=n(579),r=n(281),s={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,n){this.name=e,this.snowboard=new Proxy(t,s),this.instance=n,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),n=0;n!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...n),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instances[0][t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instances[0][n]=function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instance.prototype[t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instance.prototype[n]=function(){for(var t=arguments.length,n=new Array(t),r=0;rthis.instances.splice(this.instances.indexOf(r),1),r.construct(...n),this.instances.push(r),r}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof i.Z==!1}isSingleton(){return this.instance.prototype instanceof r.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),n=0;nthis.instances.splice(this.instances.indexOf(i),1),i.construct(...t),this.instances.push(i),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var n=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,i=new Array(e),r=0;r{const[t,n]=e;void 0!==this.defaults[t]&&(this.defaults[t]=n)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[n,i]=t;null!==this.defaults[n]&&(e[n]=i)})),e}get(e){if(void 0===e){const e=l.Z.get();return Object.entries(e).forEach((t=>{const[n,i]=t;this.snowboard.globalEvent("cookie.get",n,i,(t=>{e[n]=t}))})),e}let t=l.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,n){let i=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{i=e})),l.Z.set(e,i,h(h({},this.getDefaults()),n))}remove(e,t){l.Z.remove(e,h(h({},this.getDefaults()),t))}}class f extends r.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let n="",i=null,r=null,s="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");s='"';for(let e=1;e="0"&&e[t]<="9"){n="";for(let i=t;i="0"&&e[i]<="9"))return{originLength:n.length,body:n};n+=e[i]}throw new Error(`Broken JSON number body near ${n}`)}if("{"===e[t]||"["===e[t]){const i=[e[t]];n=e[t];for(let r=t+1;r=0?t-5:0,50)}`)}parseKey(e,t,n){let i="";for(let r=t;r="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class g extends r.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const n=(new DOMParser).parseFromString(e,"text/html"),i=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(n.getRootNode()),i?n.body.innerHTML:n.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const n=e.toLowerCase();if(this.hasPlugin(n))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof i.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[n])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[n]=new o(n,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((n=>{const[i,r]=n;if(r.isFunction())return;if(!r.dependenciesFulfilled())return;if(!r.hasMethod("listens"))return;const s=r.callMethod("listens");"string"!=typeof s[e]&&"function"!=typeof s[e]||t.push(i)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const n=this.listeners[e].indexOf(t);-1!==n&&this.listeners[e].splice(n,1)}globalEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const r=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if(!s)if("function"==typeof r)try{!1===r.apply(i,n)&&(s=!0)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof r){if(!i[r])throw new Error(`Missing "${r}" method in "${t}" plugin`);try{!1===i[r](...n)&&(s=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!s&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!s)try{!1===t(...n)&&(s=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!s}globalPromiseEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const r=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if("function"==typeof r)try{const e=r.apply(i,n);if(e instanceof Promise==!1)return;s.push(e)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof r){if(!i[r])throw new Error(`Missing "${r}" method in "${t}" plugin`);try{const e=i[r](...n);if(e instanceof Promise==!1)return;s.push(e)}catch(n){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...n);if(e instanceof Promise==!1)return;s.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===s.length?Promise.resolve():Promise.all(s)}logMessage(e,t,n){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,n);for(var i=arguments.length,r=new Array(i>3?i-3:0),s=3;s{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i1?t-1:0),i=1;i1?t-1:0),i=1;i{const t=new Proxy(new i.Z,r.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)}},function(e){e.O(0,[109],(function(){return t=236,e(e.s=t);var t}));e.O()}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[243],{579:function(e,t,n){n.d(t,{Z:function(){return i}});class i{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,n){n.d(t,{Z:function(){return s}});var i=n(579);class s extends i.Z{}},662:function(e,t){t.Z={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}}},286:function(e,t,n){n.d(t,{Z:function(){return b}});var i=n(579),s=n(281),r={get(e,t,n){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))throw new Error(`You cannot use the "${t}" Snowboard method within a plugin.`);if(e.hasPlugin(n))return function(){return Reflect.get(e,"plugins")[n].getInstance(...arguments)}}return Reflect.get(e,t,n)},has(e,t){if("string"==typeof t){const n=t.toLowerCase();if(["attachAbstracts","loadUtilities","initialise","initialiseSingletons"].includes(t))return!1;if(e.hasPlugin(n))return!0}return Reflect.has(e,t)}};class o{constructor(e,t,n){this.name=e,this.snowboard=new Proxy(t,r),this.instance=n,Object.freeze(this.instance),this.instances=[],this.singleton={initialised:!1},Object.seal(this.singleton),this.mocks={},this.originalFunctions={},Object.freeze(o.prototype),Object.freeze(this)}hasMethod(e){return!this.isFunction()&&"function"==typeof this.instance.prototype[e]}callMethod(){if(this.isFunction())return null;for(var e=arguments.length,t=new Array(e),n=0;n!this.snowboard.getPluginNames().includes(e)));throw new Error(`The "${this.name}" plugin requires the following plugins: ${e.join(", ")}`)}if(this.isSingleton())return 0===this.instances.length&&this.initialiseSingleton(...n),Object.keys(this.mocks).length>0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instances[0][t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instances[0][n]=function(){for(var t=arguments.length,n=new Array(t),s=0;s0&&(Object.entries(this.originalFunctions).forEach((e=>{const[t,n]=e;this.instance.prototype[t]=n})),Object.entries(this.mocks).forEach((t=>{const[n,i]=t;this.instance.prototype[n]=function(){for(var t=arguments.length,n=new Array(t),s=0;sthis.instances.splice(this.instances.indexOf(s),1),s.construct(...n),this.instances.push(s),s}getInstances(){return this.isFunction()?[]:this.instances}isFunction(){return"function"==typeof this.instance&&this.instance.prototype instanceof i.Z==!1}isSingleton(){return this.instance.prototype instanceof s.Z==!0}isInitialised(){return!this.isSingleton()||this.singleton.initialised}initialiseSingleton(){if(!this.isSingleton())return;for(var e=arguments.length,t=new Array(e),n=0;nthis.instances.splice(this.instances.indexOf(i),1),i.construct(...t),this.instances.push(i),this.singleton.initialised=!0}getDependencies(){return this.isFunction()||"function"!=typeof this.instance.prototype.dependencies?[]:this.instance.prototype.dependencies().map((e=>e.toLowerCase()))}dependenciesFulfilled(){const e=this.getDependencies();let t=!0;return e.forEach((e=>{this.snowboard.hasPlugin(e)||(t=!1)})),t}mock(e,t){var n=this;if(!this.isFunction()){if(!this.instance.prototype[e])throw new Error(`Function "${e}" does not exist and cannot be mocked`);this.mocks[e]=t,this.originalFunctions[e]=this.instance.prototype[e],this.isSingleton()&&0===this.instances.length&&(this.initialiseSingleton(),this.instances[0][e]=function(){for(var e=arguments.length,i=new Array(e),s=0;s{const[t,n]=e;void 0!==this.defaults[t]&&(this.defaults[t]=n)}))}getDefaults(){const e={};return Object.entries(this.defaults).forEach((t=>{const[n,i]=t;null!==this.defaults[n]&&(e[n]=i)})),e}get(e){if(void 0===e){const e=l.Z.get();return Object.entries(e).forEach((t=>{const[n,i]=t;this.snowboard.globalEvent("cookie.get",n,i,(t=>{e[n]=t}))})),e}let t=l.Z.get(e);return this.snowboard.globalEvent("cookie.get",e,t,(e=>{t=e})),t}set(e,t,n){let i=t;return this.snowboard.globalEvent("cookie.set",e,t,(e=>{i=e})),l.Z.set(e,i,h(h({},this.getDefaults()),n))}remove(e,t){l.Z.remove(e,h(h({},this.getDefaults()),t))}}class f extends s.Z{construct(){window.wnJSON=e=>this.parse(e),window.ocJSON=window.wnJSON}parse(e){const t=this.parseString(e);return JSON.parse(t)}parseString(e){let t=e.trim();if(!t.length)throw new Error("Broken JSON object.");let n="",i=null,s=null,r="";for(;t&&","===t[0];)t=t.substr(1);if('"'===t[0]||"'"===t[0]){if(t[t.length-1]!==t[0])throw new Error("Invalid string JSON object.");r='"';for(let e=1;e="0"&&e[t]<="9"){n="";for(let i=t;i="0"&&e[i]<="9"))return{originLength:n.length,body:n};n+=e[i]}throw new Error(`Broken JSON number body near ${n}`)}if("{"===e[t]||"["===e[t]){const i=[e[t]];n=e[t];for(let s=t+1;s=0?t-5:0,50)}`)}parseKey(e,t,n){let i="";for(let s=t;s="a"&&e[0]<="z"||e[0]>="A"&&e[0]<="Z"||"_"===e[0]||(e[0]>="0"&&e[0]<="9"||("$"===e[0]||e.charCodeAt(0)>255)))}isBlankChar(e){return" "===e||"\n"===e||"\t"===e}}class d extends s.Z{construct(){window.wnSanitize=e=>this.sanitize(e),window.ocSanitize=window.wnSanitize}sanitize(e,t){const n=(new DOMParser).parseFromString(e,"text/html"),i=void 0===t||"boolean"!=typeof t||t;return this.sanitizeNode(n.getRootNode()),i?n.body.innerHTML:n.innerHTML}sanitizeNode(e){if("SCRIPT"===e.tagName)return void e.remove();this.trimAttributes(e);Array.from(e.children).forEach((e=>{this.sanitizeNode(e)}))}trimAttributes(e){if(e.attributes)for(let t=0;t{this.autoInitSingletons&&this.initialiseSingletons(),this.globalEvent("ready"),this.readiness.dom=!0}))}initialiseSingletons(){Object.values(this.plugins).forEach((e=>{e.isSingleton()&&e.dependenciesFulfilled()&&e.initialiseSingleton()}))}addPlugin(e,t){const n=e.toLowerCase();if(this.hasPlugin(n))throw new Error(`A plugin called "${e}" is already registered.`);if("function"!=typeof t&&t instanceof i.Z==!1)throw new Error("The provided plugin must extend the PluginBase class, or must be a callback function.");if(void 0!==this[e]||void 0!==this[n])throw new Error("The given name is already in use for a property or method of the Snowboard class.");this.plugins[n]=new o(n,this,t),this.debug(`Plugin "${e}" registered`),Object.values(this.getPlugins()).forEach((e=>{if(e.isSingleton()&&!e.isInitialised()&&e.dependenciesFulfilled()&&e.hasMethod("listens")&&Object.keys(e.callMethod("listens")).includes("ready")&&this.readiness.dom){const t=e.callMethod("listens").ready;e.callMethod(t)}}))}removePlugin(e){const t=e.toLowerCase();this.hasPlugin(t)?(this.plugins[t].getInstances().forEach((e=>{e.destruct()})),delete this.plugins[t],delete this[t],delete this[e],this.debug(`Plugin "${e}" removed`)):this.debug(`Plugin "${e}" already removed`)}hasPlugin(e){const t=e.toLowerCase();return void 0!==this.plugins[t]}getPlugins(){return this.plugins}getPluginNames(){return Object.keys(this.plugins)}getPlugin(e){const t=e.toLowerCase();if(!this.hasPlugin(t))throw new Error(`No plugin called "${t}" has been registered.`);return this.plugins[t]}listensToEvent(e){const t=[];return Object.entries(this.plugins).forEach((n=>{const[i,s]=n;if(s.isFunction())return;if(!s.dependenciesFulfilled())return;if(!s.hasMethod("listens"))return;const r=s.callMethod("listens");"string"!=typeof r[e]&&"function"!=typeof r[e]||t.push(i)})),t}ready(e){this.readiness.dom&&e(),this.on("ready",e)}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].includes(t)||this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;const n=this.listeners[e].indexOf(t);-1!==n&&this.listeners[e].splice(n,1)}globalEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const s=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if(!r)if("function"==typeof s)try{!1===s.apply(i,n)&&(r=!0)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof s){if(!i[s])throw new Error(`Missing "${s}" method in "${t}" plugin`);try{!1===i[s](...n)&&(r=!0,this.debug(`Global event "${e}" cancelled by "${t}" plugin`))}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),!r&&this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global event "${e}"`),this.listeners[e].forEach((t=>{if(!r)try{!1===t(...n)&&(r=!0,this.debug(`Global event "${e} cancelled by an ad-hoc listener.`))}catch(t){this.error(`Error thrown in "${e}" event by an ad-hoc listener.`,t)}}))),!r}globalPromiseEvent(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i{const i=this.getPlugin(t);if(i.isFunction())return;i.isSingleton()&&0===i.getInstances().length&&i.initialiseSingleton();const s=i.callMethod("listens")[e];i.getInstances().forEach((i=>{if("function"==typeof s)try{const e=s.apply(i,n);if(e instanceof Promise==!1)return;r.push(e)}catch(n){this.error(`Error thrown in "${e}" event by "${t}" plugin.`,n)}else if("string"==typeof s){if(!i[s])throw new Error(`Missing "${s}" method in "${t}" plugin`);try{const e=i[s](...n);if(e instanceof Promise==!1)return;r.push(e)}catch(n){this.error(`Error thrown in "${e}" promise event by "${t}" plugin.`,n)}}else this.error(`Listen method for "${e}" event in "${t}" plugin is not a function or string.`)}))})),this.listeners[e]&&this.listeners[e].length>0&&(this.debug(`Found ${this.listeners[e].length} ad-hoc listener(s) for global promise event "${e}"`),this.listeners[e].forEach((t=>{try{const e=t(...n);if(e instanceof Promise==!1)return;r.push(e)}catch(t){this.error(`Error thrown in "${e}" promise event by an ad-hoc listener.`,t)}}))),0===r.length?Promise.resolve():Promise.all(r)}logMessage(e,t,n){console.groupCollapsed("%c[Snowboard]",`color: ${e}; font-weight: ${t?"bold":"normal"};`,n);for(var i=arguments.length,s=new Array(i>3?i-3:0),r=3;r{e+=1,console.log(`%c${e}:`,"color: rgb(88, 88, 88); font-weight: normal;",t)})),console.groupEnd(),console.groupCollapsed("%cTrace","color: rgb(45, 167, 199); font-weight: bold;"),console.trace(),console.groupEnd()}else console.trace();console.groupEnd()}log(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i1?t-1:0),i=1;i1?t-1:0),i=1;i{const t=new Proxy(new i.Z,s.Z);e.snowboard=t,e.Snowboard=t,e.SnowBoard=t})(window)}},function(e){e.O(0,[109],(function(){return t=236,e(e.s=t);var t}));e.O()}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/build/snowboard.data-attr.js b/modules/system/assets/js/snowboard/build/snowboard.data-attr.js
index 41fe7fbf97..39059da802 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.data-attr.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.data-attr.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[806],{579:function(e,t,r){r.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,r){r.d(t,{Z:function(){return a}});var n=r(579);class a extends n.Z{}},974:function(e,t,r){var n=r(281);function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function s(e){for(var t=1;tthis.changeHandler(e))),window.addEventListener("click",(e=>this.clickHandler(e))),window.addEventListener("keydown",(e=>this.keyDownHandler(e))),window.addEventListener("submit",(e=>this.submitHandler(e)))}disableDefaultFormValidation(){document.querySelectorAll("form[data-request]:not([data-browser-validate])").forEach((e=>{e.setAttribute("novalidate",!0)}))}detachHandlers(){window.removeEventListener("change",(e=>this.changeHandler(e))),window.removeEventListener("click",(e=>this.clickHandler(e))),window.removeEventListener("keydown",(e=>this.keyDownHandler(e))),window.removeEventListener("submit",(e=>this.submitHandler(e)))}changeHandler(e){e.target.matches("select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]")&&this.processRequestOnElement(e.target)}clickHandler(e){let t=e.target;for(;"HTML"!==t.tagName;){if(t.matches("a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]")){e.preventDefault(),this.processRequestOnElement(t);break}t=t.parentElement}}keyDownHandler(e){if(!e.target.matches("input"))return;-1!==["checkbox","color","date","datetime","datetime-local","email","image","month","number","password","radio","range","search","tel","text","time","url","week"].indexOf(e.target.getAttribute("type"))&&("Enter"===e.key&&e.target.matches("*[data-request]")?(this.processRequestOnElement(e.target),e.preventDefault(),e.stopImmediatePropagation()):e.target.matches("*[data-track-input]")&&this.trackInput(e.target))}submitHandler(e){e.target.matches("form[data-request]")&&(e.preventDefault(),this.processRequestOnElement(e.target))}processRequestOnElement(e){const t=e.dataset,r=String(t.request),n={confirm:"requestConfirm"in t?String(t.requestConfirm):null,redirect:"requestRedirect"in t?String(t.requestRedirect):null,loading:"requestLoading"in t?String(t.requestLoading):null,flash:"requestFlash"in t,files:"requestFiles"in t,browserValidate:"requestBrowserValidate"in t,form:"requestForm"in t?String(t.requestForm):null,url:"requestUrl"in t?String(t.requestUrl):null,update:"requestUpdate"in t?this.parseData(String(t.requestUpdate)):[],data:"requestData"in t?this.parseData(String(t.requestData)):[]};this.snowboard.request(e,r,n)}onAjaxSetup(e){if(!e.element)return;const t=e.element.getAttribute("name"),r=s(s({},this.getParentRequestData(e.element)),e.options.data);e.element&&e.element.matches("input, textarea, select, button")&&!e.form&&t&&!e.options.data[t]&&(r[t]=e.element.value),e.options.data=r}getParentRequestData(e){const t=[];let r={},n=e;for(;n.parentElement&&"HTML"!==n.parentElement.tagName;)t.push(n.parentElement),n=n.parentElement;return t.reverse(),t.forEach((e=>{const t=e.dataset;"requestData"in t&&(r=s(s({},r),this.parseData(t.requestData)))})),r}parseData(e){let t;if(void 0===e&&(t=""),"object"==typeof t)return t;try{return this.snowboard.jsonparser().parse(`{${e}}`)}catch(e){throw new Error(`Error parsing the data attribute on element: ${e.message}`)}}trackInput(e){const{lastValue:t}=e.dataset,r=e.dataset.trackInput||300;void 0!==t&&t===e.value||(this.resetTrackInputTimer(e),e.dataset.inputTimer=window.setTimeout((()=>{if(e.dataset.request)return void this.processRequestOnElement(e);let t=e;for(;t.parentElement&&"HTML"!==t.parentElement.tagName;)if(t=t.parentElement,"FORM"===t.tagName&&t.dataset.request){this.processRequestOnElement(t);break}}),r))}resetTrackInputTimer(e){e.dataset.inputTimer&&(window.clearTimeout(e.dataset.inputTimer),e.dataset.inputTimer=null)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the HTML data attribute AJAX request feature.");window.Snowboard.addPlugin("attributeRequest",o)}},function(e){var t;t=974,e(e.s=t)}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[806],{579:function(e,t,r){r.d(t,{Z:function(){return n}});class n{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,r){r.d(t,{Z:function(){return a}});var n=r(579);class a extends n.Z{}},974:function(e,t,r){var n=r(281);function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function s(e){for(var t=1;tthis.changeHandler(e))),window.addEventListener("click",(e=>this.clickHandler(e))),window.addEventListener("keydown",(e=>this.keyDownHandler(e))),window.addEventListener("submit",(e=>this.submitHandler(e)))}disableDefaultFormValidation(){document.querySelectorAll("form[data-request]:not([data-browser-validate])").forEach((e=>{e.setAttribute("novalidate",!0)}))}detachHandlers(){window.removeEventListener("change",(e=>this.changeHandler(e))),window.removeEventListener("click",(e=>this.clickHandler(e))),window.removeEventListener("keydown",(e=>this.keyDownHandler(e))),window.removeEventListener("submit",(e=>this.submitHandler(e)))}changeHandler(e){e.target.matches("select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]")&&this.processRequestOnElement(e.target)}clickHandler(e){let t=e.target;for(;t&&"HTML"!==t.tagName;){if(t.matches("a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]")){e.preventDefault(),this.processRequestOnElement(t);break}t=t.parentElement}}keyDownHandler(e){if(!e.target.matches("input"))return;-1!==["checkbox","color","date","datetime","datetime-local","email","image","month","number","password","radio","range","search","tel","text","time","url","week"].indexOf(e.target.getAttribute("type"))&&("Enter"===e.key&&e.target.matches("*[data-request]")?(this.processRequestOnElement(e.target),e.preventDefault(),e.stopImmediatePropagation()):e.target.matches("*[data-track-input]")&&this.trackInput(e.target))}submitHandler(e){e.target.matches("form[data-request]")&&(e.preventDefault(),this.processRequestOnElement(e.target))}processRequestOnElement(e){const t=e.dataset,r=String(t.request),n={confirm:"requestConfirm"in t?String(t.requestConfirm):null,redirect:"requestRedirect"in t?String(t.requestRedirect):null,loading:"requestLoading"in t?String(t.requestLoading):null,flash:"requestFlash"in t,files:"requestFiles"in t,browserValidate:"requestBrowserValidate"in t,form:"requestForm"in t?String(t.requestForm):null,url:"requestUrl"in t?String(t.requestUrl):null,update:"requestUpdate"in t?this.parseData(String(t.requestUpdate)):[],data:"requestData"in t?this.parseData(String(t.requestData)):[]};this.snowboard.request(e,r,n)}onAjaxSetup(e){if(!e.element)return;const t=e.element.getAttribute("name"),r=s(s({},this.getParentRequestData(e.element)),e.options.data);e.element&&e.element.matches("input, textarea, select, button")&&!e.form&&t&&!e.options.data[t]&&(r[t]=e.element.value),e.options.data=r}getParentRequestData(e){const t=[];let r={},n=e;for(;n.parentElement&&"HTML"!==n.parentElement.tagName;)t.push(n.parentElement),n=n.parentElement;return t.reverse(),t.forEach((e=>{const t=e.dataset;"requestData"in t&&(r=s(s({},r),this.parseData(t.requestData)))})),r}parseData(e){let t;if(void 0===e&&(t=""),"object"==typeof t)return t;try{return this.snowboard.jsonparser().parse(`{${e}}`)}catch(e){throw new Error(`Error parsing the data attribute on element: ${e.message}`)}}trackInput(e){const{lastValue:t}=e.dataset,r=e.dataset.trackInput||300;void 0!==t&&t===e.value||(this.resetTrackInputTimer(e),e.dataset.inputTimer=window.setTimeout((()=>{if(e.dataset.request)return void this.processRequestOnElement(e);let t=e;for(;t.parentElement&&"HTML"!==t.parentElement.tagName;)if(t=t.parentElement,"FORM"===t.tagName&&t.dataset.request){this.processRequestOnElement(t);break}}),r))}resetTrackInputTimer(e){e.dataset.inputTimer&&(window.clearTimeout(e.dataset.inputTimer),e.dataset.inputTimer=null)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the HTML data attribute AJAX request feature.");window.Snowboard.addPlugin("attributeRequest",o)}},function(e){var t;t=974,e(e.s=t)}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/build/snowboard.extras.js b/modules/system/assets/js/snowboard/build/snowboard.extras.js
index a3025fd871..5a447a9111 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.extras.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.extras.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[459],{579:function(e,t,s){s.d(t,{Z:function(){return a}});class a{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{listens(){return{ajaxLoadAssets:"load"}}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{if(document.querySelector(`script[src="${e}"]`))return void t();const a=document.createElement("script");a.setAttribute("type","text/javascript"),a.setAttribute("src",e),a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,a),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(a)}))}loadStyle(e){return new Promise(((t,s)=>{if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const a=document.createElement("link");a.setAttribute("rel","stylesheet"),a.setAttribute("href",e),a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,a),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(a)}))}loadImage(e){return new Promise(((t,s)=>{const a=new Image;a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,a),s(new Error(`Unable to load image file: "${e}"`))})),a.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s){if(e instanceof a.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e){this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()}))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().to("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s,a,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=a?this.parseDuration(a):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,a]=e;-1!==t.indexOf(s)&&i.push(a)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},820:function(e,t,s){var a=s(270),i=s(281);class r extends i.Z{dependencies(){return["flash"]}listens(){return{ready:"ready",ajaxErrorMessage:"ajaxErrorMessage",ajaxFlashMessages:"ajaxFlashMessages"}}ready(){document.querySelectorAll('[data-control="flash-message"]').forEach((e=>{this.snowboard.flash(e.innerHTML,e.dataset.flashType,e.dataset.flashDuration),e.remove()}))}ajaxErrorMessage(e){return this.snowboard.flash(e,"error"),!1}ajaxFlashMessages(e){return Object.entries(e).forEach((e=>{const[t,s]=e;this.snowboard.flash(s,t)})),!1}}class n extends i.Z{construct(){this.errorBags=[]}listens(){return{ready:"ready",ajaxStart:"clearValidation",ajaxValidationErrors:"doValidation"}}ready(){this.collectErrorBags(document)}doValidation(e,t,s){if(void 0===s.element.dataset.requestValidate)return null;if(!e)return null;return this.errorBags.filter((t=>t.form===e)).forEach((e=>{this.showErrorBag(e,t)})),!1}clearValidation(e,t){if(void 0===t.element.dataset.requestValidate)return;if(!t.form)return;this.errorBags.filter((e=>e.form===t.form)).forEach((e=>{this.hideErrorBag(e)}))}collectErrorBags(e){e.querySelectorAll("[data-validate-error], [data-validate-for]").forEach((e=>{const t=e.closest("form[data-request-validate]");if(!t)return void e.parentNode.removeChild(e);let s=null;e.matches("[data-validate-error]")&&(s=e.querySelector("[data-message]"));const a=document.createComment(""),i={element:e,form:t,validateFor:e.dataset.validateFor?e.dataset.validateFor.split(/\s*,\s*/):"*",placeholder:a,messageListElement:s?s.cloneNode(!0):null,messageListAnchor:null,customMessage:!!e.dataset.validateFor&&(""!==e.textContent||e.childNodes.length>0)};if(s){const e=document.createComment("");s.parentNode.replaceChild(e,s),i.messageListAnchor=e}e.parentNode.replaceChild(a,e),this.errorBags.push(i)}))}hideErrorBag(e){e.element.isConnected&&e.element.parentNode.replaceChild(e.placeholder,e.element)}showErrorBag(e,t){if(this.errorBagValidatesField(e,t))if(e.element.isConnected||e.placeholder.parentNode.replaceChild(e.element,e.placeholder),"*"!==e.validateFor){if(!e.customMessage){const s=Object.keys(t).filter((t=>e.validateFor.includes(t))).shift();[e.element.innerHTML]=t[s]}}else e.messageListElement?(e.element.querySelectorAll("[data-validation-message]").forEach((e=>{e.parentNode.removeChild(e)})),Object.entries(t).forEach((t=>{const[,s]=t;s.forEach((t=>{const s=e.messageListElement.cloneNode(!0);s.dataset.validationMessage="",s.innerHTML=t,e.messageListAnchor.after(s)}))}))):[e.element.innerHTML]=t[Object.keys(t).shift()]}errorBagValidatesField(e,t){return"*"===e.validateFor||Object.keys(t).filter((t=>e.validateFor.includes(t))).length>0}}var o,l=s(269),d=s(553),c=s(277),h=s(32),u=s(809),m=s(763);if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the extra plugins.");(o=window.Snowboard).addPlugin("assetLoader",u.Z),o.addPlugin("dataConfig",m.Z),o.addPlugin("extrasStyles",h.Z),o.addPlugin("transition",l.Z),o.addPlugin("flash",a.Z),o.addPlugin("flashListener",r),o.addPlugin("formValidation",n),o.addPlugin("attachLoading",d.Z),o.addPlugin("stripeLoader",c.Z)}},function(e){var t;t=820,e(e.s=t)}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[459],{579:function(e,t,s){s.d(t,{Z:function(){return a}});class a{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},281:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{}},809:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{listens(){return{ajaxLoadAssets:"load"}}dependencies(){return["url"]}async load(e){if(e.js&&e.js.length>0)for(const t of e.js)try{await this.loadScript(t)}catch(e){return Promise.reject(e)}if(e.css&&e.css.length>0)for(const t of e.css)try{await this.loadStyle(t)}catch(e){return Promise.reject(e)}if(e.img&&e.img.length>0)for(const t of e.img)try{await this.loadImage(t)}catch(e){return Promise.reject(e)}return Promise.resolve()}loadScript(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`script[src="${e}"]`))return void t();const a=document.createElement("script");a.setAttribute("type","text/javascript"),a.setAttribute("src",e),a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","script",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","script",e,a),s(new Error(`Unable to load script file: "${e}"`))})),document.body.append(a)}))}loadStyle(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);if(document.querySelector(`link[rel="stylesheet"][href="${e}"]`))return void t();const a=document.createElement("link");a.setAttribute("rel","stylesheet"),a.setAttribute("href",e),a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","style",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","style",e,a),s(new Error(`Unable to load stylesheet file: "${e}"`))})),document.head.append(a)}))}loadImage(e){return new Promise(((t,s)=>{e=this.snowboard.url().asset(e);const a=new Image;a.addEventListener("load",(()=>{this.snowboard.globalEvent("assetLoader.loaded","image",e,a),t()})),a.addEventListener("error",(()=>{this.snowboard.globalEvent("assetLoader.error","image",e,a),s(new Error(`Unable to load image file: "${e}"`))})),a.src=e}))}}},553:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{dependencies(){return["request"]}listens(){return{ajaxStart:"ajaxStart",ajaxDone:"ajaxDone"}}ajaxStart(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.add(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.add(this.getLoadingClass(t.element))}ajaxDone(e,t){if(t.element)if("FORM"===t.element.tagName){const e=t.element.querySelectorAll("[data-attach-loading]");e.length>0&&e.forEach((e=>{e.classList.remove(this.getLoadingClass(e))}))}else void 0!==t.element.dataset.attachLoading&&t.element.classList.remove(this.getLoadingClass(t.element))}getLoadingClass(e){return void 0!==e.dataset.attachLoading&&""!==e.dataset.attachLoading?e.dataset.attachLoading:"wn-loading"}}},763:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s){if(e instanceof a.Z==!1)throw new Error("You must provide a Snowboard plugin to enable data configuration");if(t instanceof HTMLElement==!1)throw new Error("Data configuration can only be extracted from HTML elements");this.instance=e,this.element=t,this.localConfig=s||{},this.instanceConfig={},this.acceptedConfigs={},this.refresh()}get(e){return void 0===e?this.instanceConfig:void 0!==this.instanceConfig[e]?this.instanceConfig[e]:void 0}set(e,t,s){if(void 0===e)throw new Error("You must provide a configuration key to set");this.instanceConfig[e]=t,!0===s&&(this.element.dataset[e]=t,this.localConfig[e]=t)}refresh(){this.acceptedConfigs=this.getAcceptedConfigs(),this.instanceConfig=this.processConfig()}getAcceptedConfigs(){return void 0!==this.instance.acceptAllDataConfigs&&!0===this.instance.acceptAllDataConfigs||void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()&&Object.keys(this.instance.defaults())}getDefaults(){return void 0!==this.instance.defaults&&"function"==typeof this.instance.defaults&&"object"==typeof this.instance.defaults()?this.instance.defaults():{}}processConfig(){const e=this.getDefaults();if(!1===this.acceptedConfigs)return e;for(const t in this.element.dataset)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.coerceValue(this.element.dataset[t]));for(const t in this.localConfig)(!0===this.acceptedConfigs||this.acceptedConfigs.includes(t))&&(e[t]=this.localConfig[t]);return e}coerceValue(e){const t=String(e);if("null"===t)return null;if("undefined"!==t){if(t.startsWith("base64:")){const e=t.replace(/^base64:/,""),s=atob(e);return this.coerceValue(s)}if(["true","yes"].includes(t.toLowerCase()))return!0;if(["false","no"].includes(t.toLowerCase()))return!1;if(/^[-+]?[0-9]+(\.[0-9]+)?$/.test(t))return Number(t);try{return this.snowboard.jsonParser().parse(t)}catch(e){return""===t||t}}}}},270:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s){if(this.message=e,this.type=t||"default",this.duration=Number(s||7),this.duration<0)throw new Error("Flash duration must be a positive number, or zero");this.clear(),this.timer=null,this.flashTimer=null,this.create()}dependencies(){return["transition"]}destruct(){null!==this.timer&&window.clearTimeout(this.timer),this.flashTimer&&this.flashTimer.remove(),this.flash&&(this.flash.remove(),this.flash=null,this.flashTimer=null),super.destruct()}create(){this.snowboard.globalEvent("flash.create",this),this.flash=document.createElement("DIV"),this.flash.innerHTML=this.message,this.flash.classList.add("flash-message",this.type),this.flash.removeAttribute("data-control"),this.flash.addEventListener("click",(()=>this.remove())),this.flash.addEventListener("mouseover",(()=>this.stopTimer())),this.flash.addEventListener("mouseout",(()=>this.startTimer())),this.duration>0?(this.flashTimer=document.createElement("DIV"),this.flashTimer.classList.add("flash-timer"),this.flash.appendChild(this.flashTimer)):this.flash.classList.add("no-timer"),document.body.appendChild(this.flash),this.snowboard.transition(this.flash,"show",(()=>{this.startTimer()}))}remove(){this.snowboard.globalEvent("flash.remove",this),this.stopTimer(),this.snowboard.transition(this.flash,"hide",(()=>{this.flash.remove(),this.flash=null,this.destruct()}))}clear(){document.querySelectorAll("body > div.flash-message").forEach((e=>e.remove()))}startTimer(){0!==this.duration&&(this.timerTrans=this.snowboard.transition(this.flashTimer,"timeout",null,`${this.duration}.0s`,!0),this.timer=window.setTimeout((()=>this.remove()),1e3*this.duration))}stopTimer(){this.timerTrans&&this.timerTrans.cancel(),this.timer&&window.clearTimeout(this.timer)}}},277:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{dependencies(){return["request"]}listens(){return{ready:"ready",ajaxStart:"ajaxStart"}}ready(){this.counter=0,this.createStripe()}ajaxStart(e,t){!1!==t.options.stripe&&(this.show(),e.then((()=>{this.hide()})).catch((()=>{this.hide()})))}createStripe(){this.indicator=document.createElement("DIV"),this.stripe=document.createElement("DIV"),this.stripeLoaded=document.createElement("DIV"),this.indicator.classList.add("stripe-loading-indicator","loaded"),this.stripe.classList.add("stripe"),this.stripeLoaded.classList.add("stripe-loaded"),this.indicator.appendChild(this.stripe),this.indicator.appendChild(this.stripeLoaded),document.body.appendChild(this.indicator)}show(){this.counter+=1;const e=this.stripe.cloneNode(!0);this.indicator.appendChild(e),this.stripe.remove(),this.stripe=e,this.counter>1||(this.indicator.classList.remove("loaded"),document.body.classList.add("wn-loading"))}hide(e){this.counter-=1,!0===e&&(this.counter=0),this.counter<=0&&(this.indicator.classList.add("loaded"),document.body.classList.remove("wn-loading"))}}},32:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(281);class i extends a.Z{listens(){return{ready:"ready"}}ready(){let e=!1;if(document.querySelectorAll('link[rel="stylesheet"]').forEach((t=>{t.href.endsWith("/modules/system/assets/css/snowboard.extras.css")&&(e=!0)})),!e){const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",this.snowboard.url().asset("/modules/system/assets/css/snowboard.extras.css")),document.head.appendChild(e)}}}},269:function(e,t,s){s.d(t,{Z:function(){return i}});var a=s(579);class i extends a.Z{construct(e,t,s,a,i){if(e instanceof HTMLElement==!1)throw new Error("A HTMLElement must be provided for transitioning");if(this.element=e,"string"!=typeof t)throw new Error("Transition name must be specified as a string");if(this.transition=t,s&&"function"!=typeof s)throw new Error("Callback must be a valid function");this.callback=s,this.duration=a?this.parseDuration(a):null,this.trailTo=!0===i,this.doTransition()}eventClasses(){for(var e=arguments.length,t=new Array(e),s=0;s{const[s,a]=e;-1!==t.indexOf(s)&&i.push(a)})),i}doTransition(){null!==this.duration&&(this.element.style.transitionDuration=this.duration),this.resetClasses(),this.eventClasses("in","active").forEach((e=>{this.element.classList.add(e)})),window.requestAnimationFrame((()=>{"0s"!==window.getComputedStyle(this.element)["transition-duration"]?(this.element.addEventListener("transitionend",(()=>this.onTransitionEnd()),{once:!0}),window.requestAnimationFrame((()=>{this.element.classList.remove(this.eventClasses("in")[0]),this.element.classList.add(this.eventClasses("out")[0])}))):(this.resetClasses(),this.callback&&this.callback.apply(this.element),this.destruct())}))}onTransitionEnd(){this.eventClasses("active",this.trailTo?"":"out").forEach((e=>{this.element.classList.remove(e)})),this.callback&&this.callback.apply(this.element),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}cancel(){this.element.removeEventListener("transitionend",(()=>this.onTransitionEnd),{once:!0}),this.resetClasses(),null!==this.duration&&(this.element.style.transitionDuration=null),this.destruct()}resetClasses(){this.eventClasses().forEach((e=>{this.element.classList.remove(e)}))}parseDuration(e){const t=/^([0-9]+(\.[0-9]+)?)(m?s)?$/.exec(e),s=Number(t[1]);return"sec"===("s"===t[3]?"sec":"msec")?1e3*s+"ms":`${Math.floor(s)}ms`}}},820:function(e,t,s){var a=s(270),i=s(281);class r extends i.Z{dependencies(){return["flash"]}listens(){return{ready:"ready",ajaxErrorMessage:"ajaxErrorMessage",ajaxFlashMessages:"ajaxFlashMessages"}}ready(){document.querySelectorAll('[data-control="flash-message"]').forEach((e=>{this.snowboard.flash(e.innerHTML,e.dataset.flashType,e.dataset.flashDuration),e.remove()}))}ajaxErrorMessage(e){return this.snowboard.flash(e,"error"),!1}ajaxFlashMessages(e){return Object.entries(e).forEach((e=>{const[t,s]=e;this.snowboard.flash(s,t)})),!1}}class n extends i.Z{construct(){this.errorBags=[]}listens(){return{ready:"ready",ajaxStart:"clearValidation",ajaxValidationErrors:"doValidation"}}ready(){this.collectErrorBags(document)}doValidation(e,t,s){if(s.element&&void 0===s.element.dataset.requestValidate)return null;if(!e)return null;return this.errorBags.filter((t=>t.form===e)).forEach((e=>{this.showErrorBag(e,t)})),!1}clearValidation(e,t){if(t.element&&void 0===t.element.dataset.requestValidate)return;if(!t.form)return;this.errorBags.filter((e=>e.form===t.form)).forEach((e=>{this.hideErrorBag(e)}))}collectErrorBags(e){e.querySelectorAll("[data-validate-error], [data-validate-for]").forEach((e=>{const t=e.closest("form[data-request-validate]");if(!t)return void e.parentNode.removeChild(e);let s=null;e.matches("[data-validate-error]")&&(s=e.querySelector("[data-message]"));const a=document.createComment(""),i={element:e,form:t,validateFor:e.dataset.validateFor?e.dataset.validateFor.split(/\s*,\s*/):"*",placeholder:a,messageListElement:s?s.cloneNode(!0):null,messageListAnchor:null,customMessage:!!e.dataset.validateFor&&(""!==e.textContent||e.childNodes.length>0)};if(s){const e=document.createComment("");s.parentNode.replaceChild(e,s),i.messageListAnchor=e}e.parentNode.replaceChild(a,e),this.errorBags.push(i)}))}hideErrorBag(e){e.element.isConnected&&e.element.parentNode.replaceChild(e.placeholder,e.element)}showErrorBag(e,t){if(this.errorBagValidatesField(e,t))if(e.element.isConnected||e.placeholder.parentNode.replaceChild(e.element,e.placeholder),"*"!==e.validateFor){if(!e.customMessage){const s=Object.keys(t).filter((t=>e.validateFor.includes(t))).shift();[e.element.innerHTML]=t[s]}}else e.messageListElement?(e.element.querySelectorAll("[data-validation-message]").forEach((e=>{e.parentNode.removeChild(e)})),Object.entries(t).forEach((t=>{const[,s]=t;s.forEach((t=>{const s=e.messageListElement.cloneNode(!0);s.dataset.validationMessage="",s.innerHTML=t,e.messageListAnchor.after(s)}))}))):[e.element.innerHTML]=t[Object.keys(t).shift()]}errorBagValidatesField(e,t){return"*"===e.validateFor||Object.keys(t).filter((t=>e.validateFor.includes(t))).length>0}}var o,l=s(269),d=s(553),c=s(277),h=s(32),u=s(809),m=s(763);if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the extra plugins.");(o=window.Snowboard).addPlugin("assetLoader",u.Z),o.addPlugin("dataConfig",m.Z),o.addPlugin("extrasStyles",h.Z),o.addPlugin("transition",l.Z),o.addPlugin("flash",a.Z),o.addPlugin("flashListener",r),o.addPlugin("formValidation",n),o.addPlugin("attachLoading",d.Z),o.addPlugin("stripeLoader",c.Z)}},function(e){var t;t=820,e(e.s=t)}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/build/snowboard.request.js b/modules/system/assets/js/snowboard/build/snowboard.request.js
index eceb5b7e04..8522d2a05d 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.request.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.request.js
@@ -1 +1 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[988],{579:function(e,t,s){s.d(t,{Z:function(){return r}});class r{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},640:function(e,t,s){var r=s(579);function o(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,r)}return s}function n(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(n(n({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void s();const r={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(r[t]=s)})),0===Object.keys(r).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(r).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{s()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,r]=e;let o=this.options.update&&this.options.update[t]?this.options.update[t]:t,n="replace";"@"===o.substr(0,1)?(n="append",o=o.substr(1)):"^"===o.substr(0,1)?(n="prepend",o=o.substr(1)):"#"!==o.substr(0,1)&&"."!==o.substr(0,1)&&(n="noop");const i=document.querySelectorAll(o);i.length>0&&i.forEach((e=>{switch(n){case"append":e.innerHTML+=r;break;case"prepend":e.innerHTML=r+e.innerHTML;break;case"noop":break;default:e.innerHTML=r}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,r,this);const t=new Event("ajaxUpdate");t.content=r,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,r]=e;t.append(s,r)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,r,o){const n=new Error(e);return n.exception=t||null,n.file=s||null,n.line=r||null,n.trace=o||[],n}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t;t=640,e(e.s=t)}]);
\ No newline at end of file
+"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[988],{579:function(e,t,s){s.d(t,{Z:function(){return r}});class r{constructor(e){this.snowboard=e}construct(){}dependencies(){return[]}listens(){return{}}destruct(){this.detach(),delete this.snowboard}destructor(){this.destruct()}}},640:function(e,t,s){var r=s(579);function o(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),s.push.apply(s,r)}return s}function n(e){for(var t=1;t{e&&this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)}))})):this.doAjax().then((e=>{if(e.cancelled)return this.cancelled=!0,void this.complete();this.responseData=e,this.processUpdate(e).then((()=>{!1===e.X_WINTER_SUCCESS?this.processError(e):this.processResponse(e)}))}),(e=>{this.responseError=e,this.processError(e)})):this.cancelled=!0}else this.cancelled=!0}dependencies(){return["cookie","jsonParser"]}checkRequest(){if(this.element&&this.element instanceof Element==!1)throw new Error("The element provided must be an Element instance");if(void 0===this.handler)throw new Error("The AJAX handler name is not specified.");if(!this.isHandlerName(this.handler))throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".')}getFetch(){return this.fetchOptions=void 0!==this.options.fetchOptions&&"object"==typeof this.options.fetchOptions?this.options.fetchOptions:{method:"POST",headers:this.headers,body:this.data,redirect:"follow",mode:"same-origin"},this.snowboard.globalEvent("ajaxFetchOptions",this.fetchOptions,this),fetch(this.url,this.fetchOptions)}doClientValidation(){return!0!==this.options.browserValidate||!this.form||!1!==this.form.checkValidity()||(this.form.reportValidity(),!1)}doAjax(){if(!1===this.snowboard.globalEvent("ajaxBeforeSend",this))return Promise.resolve({cancelled:!0});const e=new Promise(((e,t)=>{this.getFetch().then((s=>{s.ok||406===s.status?s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((t=>{e(n(n({},t),{},{X_WINTER_SUCCESS:406!==s.status,X_WINTER_RESPONSE_CODE:s.status}))}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((t=>{e(t)}),(e=>{t(this.renderError(`Unable to process response: ${e}`))})):s.headers.has("Content-Type")&&s.headers.get("Content-Type").includes("/json")?s.json().then((e=>{e.message&&e.exception?t(this.renderError(e.message,e.exception,e.file,e.line,e.trace)):t(e)}),(e=>{t(this.renderError(`Unable to parse JSON response: ${e}`))})):s.text().then((e=>{t(this.renderError(e))}),(e=>{t(this.renderError(`Unable to process response: ${e}`))}))}),(e=>{t(this.renderError(`Unable to retrieve a response from the server: ${e}`))}))}));if(this.snowboard.globalEvent("ajaxStart",e,this),this.element){const t=new Event("ajaxPromise");t.promise=e,this.element.dispatchEvent(t)}return e}processUpdate(e){return new Promise(((t,s)=>{if("function"==typeof this.options.beforeUpdate&&!1===this.options.beforeUpdate.apply(this,[e]))return void t();const r={};if(Object.entries(e).forEach((e=>{const[t,s]=e;"X_WINTER"!==t.substr(0,8)&&(r[t]=s)})),0===Object.keys(r).length)return void(e.X_WINTER_ASSETS?this.processAssets(e.X_WINTER_ASSETS).then((()=>{t()}),(()=>{s()})):t());this.snowboard.globalPromiseEvent("ajaxBeforeUpdate",e,this).then((async()=>{e.X_WINTER_ASSETS&&await this.processAssets(e.X_WINTER_ASSETS),this.doUpdate(r).then((()=>{window.requestAnimationFrame((()=>t()))}),(()=>{s()}))}),(()=>{t()}))}))}doUpdate(e){return new Promise((t=>{const s=[];Object.entries(e).forEach((e=>{const[t,r]=e;let o=this.options.update&&this.options.update[t]?this.options.update[t]:t,n="replace";"@"===o.substr(0,1)?(n="append",o=o.substr(1)):"^"===o.substr(0,1)?(n="prepend",o=o.substr(1)):"#"!==o.substr(0,1)&&"."!==o.substr(0,1)&&(n="noop");const i=document.querySelectorAll(o);i.length>0&&i.forEach((e=>{switch(n){case"append":e.innerHTML+=r;break;case"prepend":e.innerHTML=r+e.innerHTML;break;case"noop":break;default:e.innerHTML=r}s.push(e),this.snowboard.globalEvent("ajaxUpdate",e,r,this);const t=new Event("ajaxUpdate");t.content=r,e.dispatchEvent(t)}))})),this.snowboard.globalEvent("ajaxUpdateComplete",s,this),t()}))}processResponse(e){if((!this.options.success||"function"!=typeof this.options.success||!1!==this.options.success(this.responseData,this))&&!1!==this.snowboard.globalEvent("ajaxSuccess",this.responseData,this)){if(this.element){const e=new Event("ajaxDone",{cancelable:!0});if(e.responseData=this.responseData,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}this.flash&&e.X_WINTER_FLASH_MESSAGES&&this.processFlashMessages(e.X_WINTER_FLASH_MESSAGES),this.redirect||e.X_WINTER_REDIRECT?this.processRedirect(this.redirect||e.X_WINTER_REDIRECT):this.complete()}}processError(e){if((!this.options.error||"function"!=typeof this.options.error||!1!==this.options.error(this.responseError,this))&&!1!==this.snowboard.globalEvent("ajaxError",this.responseError,this)){if(this.element){const e=new Event("ajaxFail",{cancelable:!0});if(e.responseError=this.responseError,e.request=this,this.element.dispatchEvent(e),e.defaultPrevented)return}if(e instanceof Error)this.processErrorMessage(e.message);else{let t=!1;e.X_WINTER_ERROR_FIELDS&&(t=this.processValidationErrors(e.X_WINTER_ERROR_FIELDS)),e.X_WINTER_ERROR_MESSAGE&&!t&&this.processErrorMessage(e.X_WINTER_ERROR_MESSAGE)}this.complete()}}processRedirect(e){"function"==typeof this.options.handleRedirectResponse&&!1===this.options.handleRedirectResponse.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxRedirect",e,this)&&(window.addEventListener("popstate",(()=>{if(this.element){const e=document.createEvent("CustomEvent");e.eventName="ajaxRedirected",this.element.dispatchEvent(e)}}),{once:!0}),window.location.assign(e))}processErrorMessage(e){"function"==typeof this.options.handleErrorMessage&&!1===this.options.handleErrorMessage.apply(this,[e])||!1!==this.snowboard.globalEvent("ajaxErrorMessage",e,this)&&window.alert(e)}processFlashMessages(e){"function"==typeof this.options.handleFlashMessages&&!1===this.options.handleFlashMessages.apply(this,[e])||this.snowboard.globalEvent("ajaxFlashMessages",e,this)}processValidationErrors(e){return"function"==typeof this.options.handleValidationErrors&&!1===this.options.handleValidationErrors.apply(this,[this.form,e])||!1===this.snowboard.globalEvent("ajaxValidationErrors",this.form,e,this)}processAssets(e){return this.snowboard.globalPromiseEvent("ajaxLoadAssets",e)}async doConfirm(){if("function"==typeof this.options.handleConfirmMessage)return!1!==this.options.handleConfirmMessage.apply(this,[this.confirm]);if(0===this.snowboard.listensToEvent("ajaxConfirmMessage").length)return window.confirm(this.confirm);const e=this.snowboard.globalPromiseEvent("ajaxConfirmMessage",this.confirm,this);try{if(await e)return!0}catch(e){return!1}return!1}complete(){if(this.options.complete&&"function"==typeof this.options.complete&&this.options.complete(this.responseData,this),this.snowboard.globalEvent("ajaxDone",this.responseData,this),this.element){const e=new Event("ajaxAlways");e.request=this,e.responseData=this.responseData,e.responseError=this.responseError,this.element.dispatchEvent(e)}this.destruct()}get form(){return this.options.form?"string"==typeof this.options.form?document.querySelector(this.options.form):this.options.form:this.element?"FORM"===this.element.tagName?this.element:this.element.closest("form"):null}get context(){return{handler:this.handler,options:this.options}}get headers(){const e={"X-Requested-With":"XMLHttpRequest","X-WINTER-REQUEST-HANDLER":this.handler,"X-WINTER-REQUEST-PARTIALS":this.extractPartials(this.options.update||[])};return this.flash&&(e["X-WINTER-REQUEST-FLASH"]=1),this.xsrfToken&&(e["X-XSRF-TOKEN"]=this.xsrfToken),e}get loading(){return this.options.loading||!1}get url(){return this.options.url||window.location.href}get redirect(){return this.options.redirect&&this.options.redirect.length?this.options.redirect:null}get flash(){return this.options.flash||!1}get files(){return!0===this.options.files&&(void 0!==FormData||(this.snowboard.debug("This browser does not support file uploads"),!1))}get xsrfToken(){return this.snowboard.cookie().get("XSRF-TOKEN")}get data(){const e="object"==typeof this.options.data?this.options.data:{},t=new FormData(this.form||void 0);return Object.keys(e).length>0&&Object.entries(e).forEach((e=>{const[s,r]=e;t.append(s,r)})),t}get confirm(){return this.options.confirm||!1}extractPartials(e){return Object.keys(e).join("&")}renderError(e,t,s,r,o){const n=new Error(e);return n.exception=t||null,n.file=s||null,n.line=r||null,n.trace=o||[],n}isHandlerName(e){return/^(?:\w+:{2})?on[A-Z0-9]/.test(e)}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Javascript AJAX request feature.");window.Snowboard.addPlugin("request",a)}},function(e){var t;t=640,e(e.s=t)}]);
\ No newline at end of file
diff --git a/modules/system/assets/js/snowboard/build/snowboard.vendor.js b/modules/system/assets/js/snowboard/build/snowboard.vendor.js
index f331cbe693..7038746c39 100644
--- a/modules/system/assets/js/snowboard/build/snowboard.vendor.js
+++ b/modules/system/assets/js/snowboard/build/snowboard.vendor.js
@@ -1,3 +1,3 @@
-"use strict";(self.webpackChunk_wintercms_wn_system_module=self.webpackChunk_wintercms_wn_system_module||[]).push([[109],{805:function(e,t){
-/*! js-cookie v3.0.1 | MIT */
-function n(e){for(var t=1;t {
+ // Resolve script URL
+ script = this.snowboard.url().asset(script);
+
// Check that script is not already loaded
const loaded = document.querySelector(`script[src="${script}"]`);
if (loaded) {
@@ -123,11 +137,14 @@ export default class AssetLoader extends Singleton {
*
* The stylesheet will be appended before the closing `` tag.
*
- * @param {String} script
+ * @param {String} style
* @returns {Promise}
*/
loadStyle(style) {
return new Promise((resolve, reject) => {
+ // Resolve style URL
+ style = this.snowboard.url().asset(style);
+
// Check that stylesheet is not already loaded
const loaded = document.querySelector(`link[rel="stylesheet"][href="${style}"]`);
if (loaded) {
@@ -161,6 +178,9 @@ export default class AssetLoader extends Singleton {
*/
loadImage(image) {
return new Promise((resolve, reject) => {
+ // Resolve script URL
+ image = this.snowboard.url().asset(image);
+
const img = new Image();
img.addEventListener('load', () => {
this.snowboard.globalEvent('assetLoader.loaded', 'image', image, img);
diff --git a/modules/system/assets/js/snowboard/extras/FormValidation.js b/modules/system/assets/js/snowboard/extras/FormValidation.js
index f84ec37e31..52f424a357 100644
--- a/modules/system/assets/js/snowboard/extras/FormValidation.js
+++ b/modules/system/assets/js/snowboard/extras/FormValidation.js
@@ -49,7 +49,7 @@ export default class FormValidation extends Singleton {
* @returns {Boolean}
*/
doValidation(form, invalidFields, request) {
- if (request.element.dataset.requestValidate === undefined) {
+ if (request.element && request.element.dataset.requestValidate === undefined) {
return null;
}
if (!form) {
@@ -72,7 +72,7 @@ export default class FormValidation extends Singleton {
* @returns {void}
*/
clearValidation(promise, request) {
- if (request.element.dataset.requestValidate === undefined) {
+ if (request.element && request.element.dataset.requestValidate === undefined) {
return;
}
if (!request.form) {
diff --git a/modules/system/assets/js/snowboard/extras/StripeLoader.js b/modules/system/assets/js/snowboard/extras/StripeLoader.js
index 70be75d3e9..e6d2d91064 100644
--- a/modules/system/assets/js/snowboard/extras/StripeLoader.js
+++ b/modules/system/assets/js/snowboard/extras/StripeLoader.js
@@ -34,7 +34,11 @@ export default class StripeLoader extends Singleton {
this.createStripe();
}
- ajaxStart(promise) {
+ ajaxStart(promise, request) {
+ if (request.options.stripe === false) {
+ return;
+ }
+
this.show();
promise.then(() => {
diff --git a/modules/system/assets/js/snowboard/extras/StylesheetLoader.js b/modules/system/assets/js/snowboard/extras/StylesheetLoader.js
index c5bc912c15..ffe14c4461 100644
--- a/modules/system/assets/js/snowboard/extras/StylesheetLoader.js
+++ b/modules/system/assets/js/snowboard/extras/StylesheetLoader.js
@@ -31,7 +31,7 @@ export default class StylesheetLoader extends Singleton {
if (!stylesLoaded) {
const stylesheet = document.createElement('link');
stylesheet.setAttribute('rel', 'stylesheet');
- stylesheet.setAttribute('href', this.snowboard.url().to('/modules/system/assets/css/snowboard.extras.css'));
+ stylesheet.setAttribute('href', this.snowboard.url().asset('/modules/system/assets/css/snowboard.extras.css'));
document.head.appendChild(stylesheet);
}
}
diff --git a/modules/system/assets/js/snowboard/utilities/Url.js b/modules/system/assets/js/snowboard/utilities/Url.js
index 3dce11ab0b..76f6331b31 100644
--- a/modules/system/assets/js/snowboard/utilities/Url.js
+++ b/modules/system/assets/js/snowboard/utilities/Url.js
@@ -11,7 +11,9 @@ import Singleton from '../abstracts/Singleton';
export default class Url extends Singleton {
construct() {
this.foundBaseUrl = null;
+ this.foundAssetUrl = null;
this.baseUrl();
+ this.assetUrl();
}
/**
@@ -23,7 +25,7 @@ export default class Url extends Singleton {
* @returns {string}
*/
to(url) {
- const urlRegex = /[-a-z0-9_+:]+:\/\/[-a-z0-9@:%._+~#=]{1,256}\.[a-z0-9()]{1,6}\b([-a-z0-9()@:%_+.~#?&//=]*)/i;
+ const urlRegex = /^(?:[^:]+:\/\/)[-a-z0-9@:%._+~#=]{1,256}\b([-a-z0-9()@:%_+.~#?&//=]*)/i;
if (url.match(urlRegex)) {
return url;
@@ -34,6 +36,26 @@ export default class Url extends Singleton {
return `${this.baseUrl()}${theUrl}`;
}
+ /**
+ * Gets an Asset URL based on a relative path.
+ *
+ * If an absolute URL is provided, it will be returned unchanged.
+ *
+ * @param {string} url
+ * @returns {string}
+ */
+ asset(url) {
+ const urlRegex = /^(?:[^:]+:\/\/)[-a-z0-9@:%._+~#=]{1,256}\b([-a-z0-9()@:%_+.~#?&//=]*)/i;
+
+ if (url.match(urlRegex)) {
+ return url;
+ }
+
+ const theUrl = url.replace(/^\/+/, '');
+
+ return `${this.assetUrl()}${theUrl}`;
+ }
+
/**
* Helper method to get the base URL of this install.
*
@@ -74,6 +96,46 @@ export default class Url extends Singleton {
return this.foundBaseUrl;
}
+ /**
+ * Helper method to get the asset URL of this install.
+ *
+ * This determines the base URL from three sources, in order:
+ * - If Snowboard is loaded via the `{% snowboard %}` tag, it will retrieve the asset URL that
+ * is automatically included there.
+ * - If a `` tag is available, it will use the URL specified in the link tag.
+ * - Finally, it will take a guess from the current location. This will likely not work for sites
+ * that reside in subdirectories.
+ *
+ * The asset URL will always contain a trailing backslash.
+ *
+ * @returns {string}
+ */
+ assetUrl() {
+ if (this.foundAssetUrl !== null) {
+ return this.foundAssetUrl;
+ }
+
+ if (document.querySelector('script[data-module="snowboard-base"]') !== null) {
+ this.foundAssetUrl = this.validateBaseUrl(document.querySelector('script[data-module="snowboard-base"]').dataset.assetUrl);
+ return this.foundAssetUrl;
+ }
+
+ if (document.querySelector('link[rel="asset_url"]') !== null) {
+ this.foundAssetUrl = this.validateBaseUrl(document.querySelector('link[rel="asset_url"]').getAttribute('href'));
+ return this.foundAssetUrl;
+ }
+
+ const urlParts = [
+ window.location.protocol,
+ '//',
+ window.location.host,
+ '/',
+ ];
+ this.foundAssetUrl = urlParts.join('');
+
+ return this.foundAssetUrl;
+ }
+
/**
* Validates the base URL, ensuring it is a HTTP/HTTPs URL.
*
@@ -83,7 +145,7 @@ export default class Url extends Singleton {
* @param {string} url
* @returns {string}
*/
- validateBaseUrl(url) {
+ validateBaseUrl(url) {
const urlRegex = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i;
const urlParts = urlRegex.exec(url);
const protocol = urlParts[2];
diff --git a/modules/system/assets/ui/less/list.less b/modules/system/assets/ui/less/list.less
index fa64bcfa48..b6bf451157 100644
--- a/modules/system/assets/ui/less/list.less
+++ b/modules/system/assets/ui/less/list.less
@@ -106,11 +106,11 @@ table.table.data {
div.progress {
position: relative;
overflow: visible;
- height: auto;
+ height:var(--progress-height, 18px);
margin-bottom: 0;
- background-color: transparent;
+ background-color: @color-list-progress-bg;
.border-radius(0);
- .box-shadow(none);
+ .box-shadow(0 0 1px darken(@color-list-border, 50%));
div.bar {
position: absolute;
@@ -159,6 +159,10 @@ table.table.data {
a:not(.btn), span, i[class^="icon-"] {
color: @highlight-hover-text;
}
+
+ div.progress {
+ background-color: lighten(@color-list-hover-bg, 25%);
+ }
}
tr.rowlink:not(.nolink):active td {
diff --git a/modules/system/assets/ui/less/list.variables.less b/modules/system/assets/ui/less/list.variables.less
index d4d3f35a12..0452c480bd 100644
--- a/modules/system/assets/ui/less/list.variables.less
+++ b/modules/system/assets/ui/less/list.variables.less
@@ -37,5 +37,6 @@
@color-list-active-text: @highlight-active-text;
@color-list-active-sort: #c63e26;
@color-list-grid: #E4E7E8;
+@color-list-progress-bg: #F5F5F5;
@color-status-list-text: #7e8c8d;
diff --git a/modules/system/assets/ui/less/progressbar.less b/modules/system/assets/ui/less/progressbar.less
index 77bee32ae0..0ada80f0d7 100644
--- a/modules/system/assets/ui/less/progressbar.less
+++ b/modules/system/assets/ui/less/progressbar.less
@@ -44,6 +44,8 @@
// Call animation for the active one
.progress.active .progress-bar {
+ #gradient.striped(rgba(255,255,255,.15); -45deg);
+ background-size: 40px 40px;
.animation(progress-bar-stripes 2s linear infinite);
}
@@ -71,12 +73,12 @@
// WebKit
@-webkit-keyframes progress-bar-stripes {
- from { background-position: 40px 0; }
- to { background-position: 0 0; }
+ from { background-position: 0 0; }
+ to { background-position: 40px 0; }
}
// Spec and IE10+
@keyframes progress-bar-stripes {
- from { background-position: 40px 0; }
- to { background-position: 0 0; }
+ from { background-position: 0 0; }
+ to { background-position: 40px 0; }
}
diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css
index 3ed3f9a760..e6bc16e076 100644
--- a/modules/system/assets/ui/storm.css
+++ b/modules/system/assets/ui/storm.css
@@ -1106,13 +1106,13 @@ body.slim-container .control-breadcrumb{margin-left:0;margin-right:0}
body.compact-container .control-breadcrumb{margin-top:0;margin-left:0;margin-right:0}
.progress{overflow:hidden;height:9px;margin-bottom:20px;background-color:#d9dee0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:9px;color:#fff;text-align:center;background-color:#2f99da;-webkit-transition:width 0.6s ease;transition:width 0.6s ease}
-.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}
+.progress.active .progress-bar{background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px;-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}
.progress-bar-success{background-color:#31ac5f}
.progress-bar-info{background-color:#5bc0de}
.progress-bar-warning{background-color:#f0ad4e}
.progress-bar-danger{background-color:#ab2a1c}
-@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}
-@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}
+@-webkit-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}
+@keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}
.callout{font-size:13px;margin-bottom:20px}
.callout.fade{opacity:0;filter:alpha(opacity=0);-webkit-transition:all 0.5s,width 0s;transition:all 0.5s,width 0s;-webkit-transform:scale(0.9);-ms-transform:scale(0.9);transform:scale(0.9)}
.callout.fade.in{opacity:1;filter:alpha(opacity=100);-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}
@@ -2958,7 +2958,7 @@ html.cssanimations .cursor-loading-indicator.hide{display:none}
.field-recordfinder{background-color:#fff;border:1px solid #d1d6d9;overflow:hidden;position:relative;-webkit-box-shadow:inset 0 1px 0 rgba(209,214,217,0.25),0 1px 0 rgba(255,255,255,.5);box-shadow:inset 0 1px 0 rgba(209,214,217,0.25),0 1px 0 rgba(255,255,255,.5);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
.field-recordfinder .form-control{background:transparent;border-color:transparent;height:auto;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:7px 30px 7px 11px;-webkit-box-shadow:none;box-shadow:none}
.field-recordfinder .btn{background:transparent;position:absolute;right:0;top:50%;transform:translateY(-50%);height:100%;border-radius:0;color:#595959;text-shadow:none;padding-left:15px;padding-right:15px}
-.field-recordfinder .btn.clear-record{right:44px;}
+.field-recordfinder .btn.clear-record{right:44px}
.field-recordfinder .btn i{display:block;font-size:14px}
.field-recordfinder .btn:hover{color:#333}
.field-recordfinder .text-muted i{font-size:14px;position:relative;top:1px;display:inline-block;margin:0 2px}
@@ -3163,9 +3163,9 @@ table.table.data tbody th a:not(.btn){color:#666}
table.table.data tbody td a:not(.btn):hover,
table.table.data tbody th a:not(.btn):hover{text-decoration:none}
table.table.data tbody td div.progress,
-table.table.data tbody th div.progress{position:relative;overflow:visible;height:auto;margin-bottom:0;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}
+table.table.data tbody th div.progress{position:relative;overflow:visible;height:var(--progress-height,18px);margin-bottom:0;background-color:#f5f5f5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 0 1px #515a5e;box-shadow:0 0 1px #515a5e}
table.table.data tbody td div.progress div.bar,
-table.table.data tbody th div.progress div.bar{position:absolute;left:-15px;top:-11px;bottom:-11px;background:#0181b9;opacity:0.3;filter:alpha(opacity=30)}
+table.table.data tbody th div.progress div.bar{position:absolute;left:-15px;top:-11px;bottom:-11px;background:#f5f5f5;opacity:0.3;filter:alpha(opacity=30)}
table.table.data tbody td div.progress a,
table.table.data tbody th div.progress a{position:relative}
table.table.data tbody tr:first-child th,
@@ -3183,6 +3183,8 @@ table.table.data tbody tr.rowlink:not(.nolink):hover td span,
table.table.data tbody tr:not(.no-data).selected td span,
table.table.data tbody tr.rowlink:not(.nolink):hover td i[class^="icon-"],
table.table.data tbody tr:not(.no-data).selected td i[class^="icon-"]{color:#fff}
+table.table.data tbody tr.rowlink:not(.nolink):hover td div.progress,
+table.table.data tbody tr:not(.no-data).selected td div.progress{background-color:#bbdcf3}
table.table.data tbody tr.rowlink:not(.nolink):active td{background:#3498db !important;color:#fff}
table.table.data tbody tr.hidden td,
table.table.data tbody tr.hidden th,
@@ -3685,4 +3687,4 @@ ul.autocomplete.dropdown-menu.inspector-autocomplete li a{padding:5px 12px;white
.clockpicker-plate{border:none}
.clockpicker-hours .clockpicker-tick{font-size:12px}
.clockpicker-hours .clockpicker-tick.tick-inner{font-size:16px}
-.clockpicker-minutes .clockpicker-tick{font-size:16px}
+.clockpicker-minutes .clockpicker-tick{font-size:16px}
\ No newline at end of file
diff --git a/modules/system/behaviors/SettingsModel.php b/modules/system/behaviors/SettingsModel.php
index cf7c8b06ff..8a15b2d4ec 100644
--- a/modules/system/behaviors/SettingsModel.php
+++ b/modules/system/behaviors/SettingsModel.php
@@ -5,6 +5,7 @@
use Cache;
use Log;
use Exception;
+use Illuminate\Database\QueryException;
use System\Classes\ModelBehavior;
/**
@@ -108,9 +109,8 @@ public function resetDefault()
/**
* Checks if the model has been set up previously, intended as a static method
- * @return bool
*/
- public function isConfigured()
+ public function isConfigured(): bool
{
return App::hasDatabase() && $this->getSettingsRecord() !== null;
}
@@ -127,7 +127,15 @@ public function getSettingsRecord()
$query = $query->remember($this->cacheTtl, $this->getCacheKey());
}
- $record = $query->first();
+ try {
+ $record = $query->first();
+ } catch (QueryException $ex) {
+ // SQLSTATE[42S02]: Base table or view not found - migrations haven't run yet
+ if ($ex->getCode() === '42S02') {
+ $record = null;
+ traceLog($ex);
+ }
+ }
return $record ?: null;
}
diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php
index 4d65654a28..acf3c7ece1 100644
--- a/modules/system/classes/CombineAssets.php
+++ b/modules/system/classes/CombineAssets.php
@@ -162,7 +162,7 @@ public function init()
$this->registerAlias('framework.extras', '~/modules/system/assets/css/framework.extras.css');
$this->registerAlias('framework.extras.css', '~/modules/system/assets/css/framework.extras.css');
- $snowboardBase = (Config::get('develop.debugSnowboard', Config::get('app.debug', false)) === true)
+ $snowboardBase = (Config::get('develop.debugSnowboard', false) === true)
? 'snowboard.base.debug.js'
: 'snowboard.base.js';
$this->registerAlias('snowboard.base', '~/modules/system/assets/js/snowboard/build/' . $snowboardBase);
diff --git a/modules/system/classes/FileManifest.php b/modules/system/classes/FileManifest.php
index 5a0522b3c5..d03e5cd038 100644
--- a/modules/system/classes/FileManifest.php
+++ b/modules/system/classes/FileManifest.php
@@ -166,7 +166,7 @@ protected function findFiles(string $basePath): array
*/
protected function getFilename(string $file): string
{
- return str_replace($this->root, '', $file);
+ return substr($file, strlen($this->root));
}
/**
diff --git a/modules/system/classes/MarkupManager.php b/modules/system/classes/MarkupManager.php
index 95831351b4..63c580aac1 100644
--- a/modules/system/classes/MarkupManager.php
+++ b/modules/system/classes/MarkupManager.php
@@ -1,17 +1,16 @@
getMediaPath($newPath);
+ // If the file extension is changed to SVG, ensure that it has been sanitized
+ $oldExt = pathinfo($oldPath, PATHINFO_EXTENSION);
+ $newExt = pathinfo($newPath, PATHINFO_EXTENSION);
+ if ($oldExt !== $newExt && strtolower($newExt) === 'svg') {
+ $contents = $this->getStorageDisk()->get($fullOldPath);
+ $contents = Svg::sanitize($contents);
+ $this->getStorageDisk()->put($fullOldPath, $contents);
+ }
+
return $this->getStorageDisk()->move($fullOldPath, $fullNewPath);
}
diff --git a/modules/system/classes/PluginManager.php b/modules/system/classes/PluginManager.php
index cd39f86472..5bc1d61fc1 100644
--- a/modules/system/classes/PluginManager.php
+++ b/modules/system/classes/PluginManager.php
@@ -662,8 +662,7 @@ protected function loadDisabled(): void
// Check the database for disabled plugins
if (
- $this->app->hasDatabase()
- && Schema::hasTable('system_plugin_versions')
+ $this->app->hasDatabaseTable('system_plugin_versions')
) {
$userDisabled = Db::table('system_plugin_versions')->where('is_disabled', 1)->lists('code') ?? [];
foreach ($userDisabled as $code) {
diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php
index ef9dd233a2..12f5b86f2e 100644
--- a/modules/system/classes/UpdateManager.php
+++ b/modules/system/classes/UpdateManager.php
@@ -934,6 +934,11 @@ public function requestServerData($uri, $postData = [])
$this->applyHttpAttributes($http, $postData);
});
+ // @TODO: Refactor when marketplace API finalized
+ if ($result->body === 'Package not found') {
+ $result->code = 500;
+ }
+
if ($result->code == 404) {
throw new ApplicationException(Lang::get('system::lang.server.response_not_found'));
}
diff --git a/modules/system/console/CreateJob.php b/modules/system/console/CreateJob.php
index 51e6724095..791da9faa5 100644
--- a/modules/system/console/CreateJob.php
+++ b/modules/system/console/CreateJob.php
@@ -15,7 +15,7 @@ class CreateJob extends BaseScaffoldCommand
protected $signature = 'create:job
{plugin : The name of the plugin. (eg: Winter.Blog)}
{name : The name of the job class to generate. (eg: ImportPosts)}
- {--s|sync : Overwrite existing files with generated files.}
+ {--s|sync : Generates a non-queueable job.}
{--f|force : Overwrite existing files with generated files.}
{--uninspiring : Disable inspirational quotes}
';
diff --git a/modules/system/console/CreateMigration.php b/modules/system/console/CreateMigration.php
index 5d730e6ad6..886cb82848 100644
--- a/modules/system/console/CreateMigration.php
+++ b/modules/system/console/CreateMigration.php
@@ -1,9 +1,11 @@
option('create') && $this->option('update')) {
- $this->error('The create & update options cannot both be set at the same time');
- return false;
+ throw new InvalidArgumentException('The create & update options cannot both be set at the same time');
}
if ($this->option('create')) {
@@ -173,6 +174,10 @@ protected function prepareVars(): array
throw new InvalidArgumentException('The table or model options are required when using the create or update options');
}
+ if (($table || $model) && !in_array($scaffold, ['create', 'update'])) {
+ throw new InvalidArgumentException('One of create or update option is required when using the model or table options');
+ }
+
$this->stubs = $this->migrationScaffolds[$scaffold];
if (!empty($this->option('for-version'))) {
@@ -192,6 +197,9 @@ protected function prepareVars(): array
'version' => $version,
];
+ if (!empty($model)) {
+ $vars['model'] = $model;
+ }
if (!empty($table)) {
$vars['table'] = $table;
}
@@ -199,6 +207,90 @@ protected function prepareVars(): array
return $vars;
}
+ /**
+ * Create vars for model fields mappings so they can be used in update/create stubs
+ */
+ protected function processVars(array $vars): array
+ {
+ $vars = parent::processVars($vars);
+
+ // --model option needed below
+ if (empty($vars['model'])) {
+ return $vars;
+ }
+
+ $vars['fields'] = [];
+
+ $fields_path = $vars['plugin_url'] . '/models/' . $vars['lower_model'] . '/fields.yaml';
+ $fields = Yaml::parseFile(plugins_path($fields_path));
+
+ $modelName = $vars['plugin_namespace'] . '\\Models\\' . $vars['model'];
+
+ $vars['model'] = $model = new $modelName();
+
+ foreach (['fields', 'tabs', 'secondaryTabs'] as $type) {
+ if (!isset($fields[$type])) {
+ continue;
+ }
+ if ($type === 'fields') {
+ $fieldList = $fields[$type];
+ } else {
+ $fieldList = $fields[$type]['fields'];
+ }
+
+ foreach ($fieldList as $field => $config) {
+ if (str_contains($field, '@')) {
+ list($field, $context) = explode('@', $field);
+ }
+
+ $type = $config['type'] ?? 'text';
+
+ if (str_starts_with($field, '_')
+ || $field === $model->getKeyName()
+ || str_contains($field, '[')
+ || in_array($type, ['fileupload', 'relation', 'relationmanager', 'repeater', 'section', 'hint'])
+ || in_array($field, $model->purgeable ?? [])
+ || $model->getRelationType($field)
+ ) {
+ continue;
+ }
+
+ $vars['fields'][$field] = $this->mapFieldType($field, $config, $model);
+ }
+ }
+
+ foreach ($model->getRelationDefinitions() as $relationType => $definitions) {
+ if (in_array($relationType, ['belongsTo', 'hasOne'])) {
+ foreach (array_keys($definitions) as $relation) {
+ $vars['fields'][$relation . '_id'] = [
+ 'type' => 'foreignId',
+ 'index' => true,
+ 'required' => true,
+ ];
+ }
+ }
+ }
+
+ if ($model->methodExists('getSortOrderColumn')) {
+ $field = $model->getSortOrderColumn();
+ $vars['fields'][$field] = [
+ 'type' => 'unsignedinteger',
+ 'required' => false,
+ 'index' => true,
+ ];
+ }
+
+ $vars['primaryKey'] = $model->getKeyName();
+ $vars['jsonable'] = $model->getJsonable();
+ $vars['timestamps'] = $model->timestamps;
+
+ if ($morphable = $model->morphTo) {
+ $vars['morphable'] = array_keys($morphable);
+ }
+
+ return $vars;
+ }
+
/**
* Get the next version number based on the current number.
*/
@@ -209,4 +301,55 @@ protected function getNextVersion($currentVersion): string
$parts[count($parts) - 1] = (int) $parts[count($parts) - 1] + 1;
return 'v' . implode('.', $parts);
}
+
+ /**
+ * Maps model fields config to DB Schema column types.
+ */
+ protected function mapFieldType(string $fieldName, array $fieldConfig, ?Model $model = null) : array
+ {
+ switch ($fieldConfig['type'] ?? 'text') {
+ case 'checkbox':
+ case 'switch':
+ $dbType = 'boolean';
+ break;
+ case 'number':
+ $dbType = 'double';
+ if (isset($fieldConfig['step']) && is_int($fieldConfig['step'])) {
+ $dbType = 'integer';
+ }
+ if ($dbType === 'integer' && isset($fieldConfig['min']) && $fieldConfig['min'] >= 0) {
+ $dbType = 'unsignedInteger';
+ }
+ break;
+ case 'range':
+ $dbType = 'unsignedInteger';
+ break;
+ case 'datepicker':
+ $dbType = $fieldConfig['mode'] ?? 'datetime';
+ break;
+ case 'markdown':
+ $dbType = 'mediumText';
+ break;
+ case 'textarea':
+ $dbType = 'text';
+ break;
+ default:
+ $dbType = 'string';
+ }
+
+ if ($model) {
+ $rule = array_get($model->rules ?? [], $fieldName, '');
+ $rule = is_array($rule) ? implode(',', $rule) : $rule;
+
+ $required = str_contains($rule, 'required') ? true : $fieldConfig['required'] ?? false;
+ } else {
+ $required = $fieldConfig['required'] ?? false;
+ }
+
+ return [
+ 'type' => $dbType,
+ 'required' => $required,
+ 'index' => in_array($fieldName, ["slug"]) or str_ends_with($fieldName, "_id"),
+ ];
+ }
}
diff --git a/modules/system/console/CreateTest.php b/modules/system/console/CreateTest.php
new file mode 100644
index 0000000000..c973299252
--- /dev/null
+++ b/modules/system/console/CreateTest.php
@@ -0,0 +1,119 @@
+(eg: Winter.Blog)}
+ {name : The name of the test class to generate. Test will be automatically added to the end. Can also be a relative path to a class to generate a test for. (eg: Components\Posts)}
+ {--u|unit : Generate a Unit test (defaults to generating Feature tests).}
+ {--p|pest : Generate a Pest PHP test (defaults to generating PHPUnit tests).}
+ {--f|force : Overwrite existing files with generated files.}
+ {--uninspiring : Disable inspirational quotes}
+ ';
+
+ /**
+ * @var string The console command description.
+ */
+ protected $description = 'Creates a new test class.';
+
+ /**
+ * @var array List of commands that this command replaces (aliases)
+ */
+ protected $replaces = [
+ 'make:test',
+ ];
+
+ /**
+ * @var string The type of class being generated.
+ */
+ protected $type = 'Test';
+
+ /**
+ * @var array Stub files to make a plugin testable
+ */
+ protected $pluginStubs = [
+ 'scaffold/test/test.plugin.stub' => 'tests/Unit/PluginTest.php',
+ 'scaffold/test/phpunit.stub' => 'phpunit.xml',
+ ];
+
+ /**
+ * Adds controller & model lang helpers to the vars
+ */
+ protected function processVars($vars): array
+ {
+ $vars = parent::processVars($vars);
+
+ // Enable testing on the plugin if it isn't already
+ if (!$this->files->exists($this->getDestinationPath() . '/phpunit.xml')) {
+ $this->stubs = array_merge($this->pluginStubs, $this->stubs);
+ }
+
+ // Populate Pest.php if it doesn't exist
+ $isPest = $this->option('pest');
+ if ($isPest && !$this->files->exists($this->getDestinationPath() . '/tests/Pest.php')) {
+ $this->stubs = array_merge($this->stubs, [
+ 'scaffold/test/pest.init.stub' => 'tests/Pest.php',
+ ]);
+ }
+
+ $prefix = $isPest ? 'pest' : 'test';
+ $type = $this->option('unit') ? 'Unit' : 'Feature';
+ $suffix = $this->option('unit') ? '.unit.stub' : '.stub';
+
+ $name = $this->argument('name');
+ $class = $vars['plugin_namespace'] . '\\' . $name;
+
+ // provided name is a class in the plugin
+ if (class_exists($class)) {
+ // Get the public methods to stub out tests for
+ $reflection = new \ReflectionClass($class);
+ $publicMethods = [];
+ $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC) ?: [];
+ foreach ($methods as $method) {
+ if ($method->class === $class) {
+ $publicMethods[] = $method->name;
+ }
+ }
+ $vars['public_methods'] = $publicMethods;
+
+ // Generate the necessary stub variables
+ $namePieces = explode('\\', $name);
+ $vars['tested_class_full'] = $class;
+ $vars['tested_class'] = array_pop($namePieces);
+ $testClass = $vars['tested_class'] . 'Test';
+ $suffix = '.class.stub';
+ if (count($namePieces)) {
+ $type .= '\\' . implode('\\', $namePieces);
+ }
+ // sometimes a name is just a name. Move on.
+ } else {
+ $testClass = $name . 'Test';
+ }
+
+ // Just in case :)
+ $testClass = Str::replace('TestTest', 'Test', $testClass);
+
+ $folder = str_replace('\\', '/', $type);
+ $vars['test_class'] = $testClass;
+ $vars['test_namespace'] = "{$vars['plugin_namespace']}\\Tests\\$type";
+
+ $this->stubs["scaffold/test/{$prefix}{$suffix}"] = "tests/$folder/$testClass.php";
+
+ return array_merge($vars, [
+ 'test_namespace' => "{$vars['plugin_namespace']}\\Tests\\$type",
+ ]);
+ }
+}
diff --git a/modules/system/console/MixList.php b/modules/system/console/MixList.php
index 77eb632642..74fcb6e5c7 100644
--- a/modules/system/console/MixList.php
+++ b/modules/system/console/MixList.php
@@ -40,21 +40,22 @@ public function handle(): int
$errors = [];
+ $rows = [];
foreach ($packages as $name => $package) {
- if ($package['ignored'] ?? false) {
- $this->warn($name);
- } else {
- $this->info($name);
- }
-
- $this->line(' Path: ' . $package['path']);
- $this->line(' Configuration: ' . $package['mix']);
+ $rows[] = [
+ 'name' => $name,
+ 'active' => $package['ignored'] ?? false ? 'No>' : 'Yes',
+ 'path' => $package['path'],
+ 'configuration' => $package['mix'],
+ ];
if (!File::exists($package['mix'])) {
$errors[] = "The mix file for $name doesn't exist, try running artisan mix:install";
}
}
+ $this->table(['Name', 'Active', 'Path', 'Configuration'], $rows);
+
$this->line('');
if (!empty($errors)) {
diff --git a/modules/system/console/PluginList.php b/modules/system/console/PluginList.php
index 21473f4cb2..f7883c34d4 100644
--- a/modules/system/console/PluginList.php
+++ b/modules/system/console/PluginList.php
@@ -1,9 +1,9 @@
output);
-
- // Set the table headers.
- $table->setHeaders([
- 'Plugin name', 'Version', 'Updates enabled', 'Plugin enabled'
- ]);
-
- // Create a new TableSeparator instance.
- $separator = new TableSeparator;
-
- $pluginTable = [];
-
- $row = 0;
+ $rows = [];
foreach ($allPlugins as $plugin) {
- $row++;
-
- $pluginTable[] = [$plugin->code, $plugin->version, (!$plugin->is_frozen) ? 'Yes': 'No', (!$plugin->is_disabled) ? 'Yes': 'No'];
-
- if ($row < $pluginsCount) {
- $pluginTable[] = $separator;
- }
+ $rows[] = [
+ $plugin->code,
+ $plugin->version,
+ (!$plugin->is_frozen) ? 'Yes': 'No>',
+ (!$plugin->is_disabled) ? 'Yes': 'No>',
+ ];
}
- // Set the contents of the table.
- $table->setRows($pluginTable);
-
- // Render the table to the output.
- $table->render();
+ $this->table(['Plugin name', 'Version', 'Updates enabled', 'Plugin enabled'], $rows);
}
}
diff --git a/modules/system/console/WinterInstall.php b/modules/system/console/WinterInstall.php
index 5edc260aa7..5f600f6402 100644
--- a/modules/system/console/WinterInstall.php
+++ b/modules/system/console/WinterInstall.php
@@ -1,18 +1,17 @@
configWriter = new ConfigWriter;
-
- // Register aliases for backwards compatibility with October
- $this->setAliases(['october:install']);
}
/**
@@ -64,7 +67,7 @@ public function handle()
$this->displayIntro();
if (
- App::hasDatabase() &&
+ $this->laravel->hasDatabase() &&
!$this->confirm('Application appears to be installed already. Continue anyway?', false)
) {
return;
diff --git a/modules/system/console/WinterManifest.php b/modules/system/console/WinterManifest.php
index 2860a174b3..e406dce476 100644
--- a/modules/system/console/WinterManifest.php
+++ b/modules/system/console/WinterManifest.php
@@ -227,7 +227,7 @@ public function handle()
protected function getVersionInt(string $version)
{
// Get major.minor.patch versions
- if (!preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $versionParts)) {
+ if (!preg_match('/^v?([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $versionParts)) {
throw new ApplicationException('Invalid version string - must be of the format "major.minor.path"');
}
diff --git a/modules/system/console/WinterTest.php b/modules/system/console/WinterTest.php
index 4c35d0a70b..8a0eb2375d 100644
--- a/modules/system/console/WinterTest.php
+++ b/modules/system/console/WinterTest.php
@@ -8,6 +8,7 @@
use System\Classes\PluginManager;
use Winter\Storm\Exception\ApplicationException;
use Winter\Storm\Filesystem\PathResolver;
+use Winter\Storm\Support\Str;
/**
* Console command to run tests for plugins and modules.
@@ -65,6 +66,14 @@ public function __construct()
$this->ignoreValidationErrors();
}
+ /**
+ * Determines if Pest is being used.
+ */
+ protected function usingPest(): bool
+ {
+ return class_exists(\Pest\Laravel\PestServiceProvider::class);
+ }
+
/**
* Execute the console command.
*
@@ -135,8 +144,9 @@ protected function execPhpUnit(string $config, array $args): int
{
// Find and bind the phpunit executable
if (!$this->phpUnitExec) {
+ $bin = $this->usingPest() ? 'pest' : 'phpunit';
$this->phpUnitExec = (new ExecutableFinder())
- ->find('phpunit', base_path('vendor/bin/phpunit'), [base_path('vendor')]);
+ ->find($bin, base_path("vendor/bin/$bin"), [base_path('vendor')]);
}
// Resolve the configuration path based on the current working directory
@@ -163,8 +173,19 @@ protected function execPhpUnit(string $config, array $args): int
));
}
+ $testDirectory = Str::after(dirname($config), base_path() . DIRECTORY_SEPARATOR) . '/tests';
+
+ $generatedArgs = [
+ $this->phpUnitExec,
+ '--configuration=' . $config,
+ '--bootstrap=' . $bootstrapPath,
+ ];
+ if ($this->usingPest()) {
+ $generatedArgs[] = '--test-directory=' . $testDirectory;
+ }
+
$process = new Process(
- array_merge([$this->phpUnitExec, '--configuration=' . $config, '--bootstrap=' . $bootstrapPath], $args),
+ array_merge($generatedArgs, $args),
base_path(),
[
'APP_ENV' => 'testing',
diff --git a/modules/system/console/WinterVersion.php b/modules/system/console/WinterVersion.php
index a7cfa1f7d7..4f8d49e831 100644
--- a/modules/system/console/WinterVersion.php
+++ b/modules/system/console/WinterVersion.php
@@ -1,6 +1,5 @@
comment('*** Detecting Winter CMS build...');
- if (!App::hasDatabase()) {
+ if (!$this->laravel->hasDatabase()) {
$build = UpdateManager::instance()->getBuildNumberManually($this->option('changes'));
// Skip setting the build number if no database is detected to set it within
diff --git a/modules/system/console/scaffold/command/command.stub b/modules/system/console/scaffold/command/command.stub
index 24ef8b2f3e..c7dbd8367c 100644
--- a/modules/system/console/scaffold/command/command.stub
+++ b/modules/system/console/scaffold/command/command.stub
@@ -1,4 +1,6 @@
-increments('{{ primaryKey }}');
+{% else %}
$table->id();
+{% endif %}
+{% for field,config in fields %}
+ $table->{{ config.type }}('{{ field }}'){{ config.required == false ? '->nullable()' }}{{ config.index ? '->index()' }};
+{% endfor %}
+{% for field in jsonable %}
+ $table->mediumText('{{ field }}')->nullable();
+{% endfor %}
+{% for field in morphable %}
+ $table->nullableMorphs('{{ field }}', 'morphable_index');
+{% endfor %}
+{% if not model or timestamps %}
$table->timestamps();
+{% endif %}
});
}
diff --git a/modules/system/console/scaffold/migration/migration.update.stub b/modules/system/console/scaffold/migration/migration.update.stub
index 6703716169..7f69644ca4 100644
--- a/modules/system/console/scaffold/migration/migration.update.stub
+++ b/modules/system/console/scaffold/migration/migration.update.stub
@@ -14,7 +14,15 @@ return new class extends Migration
public function up()
{
Schema::table('{{ table }}', function (Blueprint $table) {
- //
+{% for field,config in fields %}
+ $table->{{ config.type }}('{{ field }}'){{ config.required == false ? '->nullable()' }}{{ config.index ? '->index()' }};
+{% endfor %}
+{% for field in jsonable %}
+ $table->mediumText('{{ field }}')->nullable();
+{% endfor %}
+{% for field in morphable %}
+ $table->nullableMorphs('{{ field }}', 'morphable_index');
+{% endfor %}
});
}
@@ -26,7 +34,15 @@ return new class extends Migration
public function down()
{
Schema::table('{{ table }}', function (Blueprint $table) {
- //
+{% for field,config in fields %}
+ $table->dropColumn('{{ field }}');
+{% endfor %}
+{% for field in jsonable %}
+ $table->dropColumn('{{ field }}');
+{% endfor %}
+{% for field in morphable %}
+ $table->dropColumn('{{ field }}');
+{% endfor %}
});
}
};
diff --git a/modules/system/console/scaffold/model/model.stub b/modules/system/console/scaffold/model/model.stub
index 49615c049f..fe022e7953 100644
--- a/modules/system/console/scaffold/model/model.stub
+++ b/modules/system/console/scaffold/model/model.stub
@@ -1,4 +1,6 @@
-instance = $this->app->make({{ tested_class }}::class);
+});
+
+{% for method in public_methods %}
+it('the {{ method }} method does something', function () {
+ // $this->assertTrue(method_exists($this->instance, '{{ method }}'));
+});
+{% endfor %}
diff --git a/modules/system/console/scaffold/test/pest.init.stub b/modules/system/console/scaffold/test/pest.init.stub
new file mode 100644
index 0000000000..e5ed43b8a5
--- /dev/null
+++ b/modules/system/console/scaffold/test/pest.init.stub
@@ -0,0 +1,45 @@
+in('Feature');
+
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
+
+// expect()->extend('toBeOne', function () {
+// return $this->toBe(1);
+// });
+
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
+
+// function something()
+// {
+// // ..
+// }
diff --git a/modules/system/console/scaffold/test/pest.stub b/modules/system/console/scaffold/test/pest.stub
new file mode 100644
index 0000000000..b46239fd0e
--- /dev/null
+++ b/modules/system/console/scaffold/test/pest.stub
@@ -0,0 +1,7 @@
+get('/');
+
+ $response->assertStatus(200);
+});
diff --git a/modules/system/console/scaffold/test/pest.unit.stub b/modules/system/console/scaffold/test/pest.unit.stub
new file mode 100644
index 0000000000..61cd84c327
--- /dev/null
+++ b/modules/system/console/scaffold/test/pest.unit.stub
@@ -0,0 +1,5 @@
+toBeTrue();
+});
diff --git a/modules/system/console/scaffold/test/phpunit.stub b/modules/system/console/scaffold/test/phpunit.stub
new file mode 100644
index 0000000000..e7877ce389
--- /dev/null
+++ b/modules/system/console/scaffold/test/phpunit.stub
@@ -0,0 +1,25 @@
+
+
+
+
+ ./tests/Unit
+
+
+ ./tests/Feature
+
+
+
+
+
+
+
+
diff --git a/modules/system/console/scaffold/test/test.class.stub b/modules/system/console/scaffold/test/test.class.stub
new file mode 100644
index 0000000000..b710e26a37
--- /dev/null
+++ b/modules/system/console/scaffold/test/test.class.stub
@@ -0,0 +1,34 @@
+instance = $this->app->make({{ tested_class }}::class);
+ }
+{% for method in public_methods %}
+
+ /**
+ * Test for the {{ method }} method
+ */
+ public function test_{{ method }}()
+ {
+ $this->assertTrue(method_exists($this->instance, '{{ method }}'));
+ }
+{% endfor %}
+}
diff --git a/modules/system/console/scaffold/test/test.plugin.stub b/modules/system/console/scaffold/test/test.plugin.stub
new file mode 100644
index 0000000000..50f2ff537c
--- /dev/null
+++ b/modules/system/console/scaffold/test/test.plugin.stub
@@ -0,0 +1,37 @@
+plugin = new Plugin($this->createApplication());
+ }
+
+ public function testPluginDetails()
+ {
+ $details = $this->plugin->pluginDetails();
+
+ $this->assertIsArray($details);
+ $this->assertArrayHasKey('name', $details);
+ $this->assertArrayHasKey('description', $details);
+ $this->assertArrayHasKey('icon', $details);
+ $this->assertArrayHasKey('author', $details);
+
+ $this->assertEquals('{{ author }}', $details['author']);
+ }
+
+ public function testRegisterPermissions()
+ {
+ $permissions = $this->plugin->registerPermissions();
+
+ $this->assertIsArray($permissions);
+ }
+}
diff --git a/modules/system/console/scaffold/test/test.stub b/modules/system/console/scaffold/test/test.stub
new file mode 100644
index 0000000000..5f6ee0ab6f
--- /dev/null
+++ b/modules/system/console/scaffold/test/test.stub
@@ -0,0 +1,18 @@
+get('/');
+
+ $response->assertStatus(200);
+ }
+}
diff --git a/modules/system/console/scaffold/test/test.unit.stub b/modules/system/console/scaffold/test/test.unit.stub
new file mode 100644
index 0000000000..321021f3d6
--- /dev/null
+++ b/modules/system/console/scaffold/test/test.unit.stub
@@ -0,0 +1,16 @@
+assertTrue(true);
+ }
+}
diff --git a/modules/system/database/migrations/2023_09_24_000028_Db_System_Sessions_Indexes.php b/modules/system/database/migrations/2023_09_24_000028_Db_System_Sessions_Indexes.php
new file mode 100644
index 0000000000..2eecc3c6d3
--- /dev/null
+++ b/modules/system/database/migrations/2023_09_24_000028_Db_System_Sessions_Indexes.php
@@ -0,0 +1,23 @@
+index(['last_activity']);
+ $table->index(['user_id']);
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('sessions', function ($table) {
+ $table->dropIndex(['last_activity']);
+ $table->dropIndex(['user_id']);
+ });
+ }
+};
diff --git a/modules/system/database/migrations/2023_10_20_000029_Db_Jobs_Batches.php b/modules/system/database/migrations/2023_10_20_000029_Db_Jobs_Batches.php
new file mode 100644
index 0000000000..49bd445229
--- /dev/null
+++ b/modules/system/database/migrations/2023_10_20_000029_Db_Jobs_Batches.php
@@ -0,0 +1,33 @@
+getTableName(), function (Blueprint $table) {
+ $table->string('id')->primary();
+ $table->string('name');
+ $table->integer('total_jobs');
+ $table->integer('pending_jobs');
+ $table->integer('failed_jobs');
+ $table->longText('failed_job_ids');
+ $table->mediumText('options')->nullable();
+ $table->integer('cancelled_at')->nullable();
+ $table->integer('created_at');
+ $table->integer('finished_at')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists($this->getTableName());
+ }
+
+ protected function getTableName()
+ {
+ return Config::get('queue.batching.table', 'job_batches');
+ }
+}
diff --git a/modules/system/helpers/View.php b/modules/system/helpers/View.php
index 21195bec8e..5be25b2d69 100644
--- a/modules/system/helpers/View.php
+++ b/modules/system/helpers/View.php
@@ -37,4 +37,14 @@ public static function getGlobalVars()
return static::$globalVarCache = $vars;
}
+
+ /**
+ * Clears the static cache for global variables.
+ *
+ * @return void
+ */
+ public static function clearVarCache()
+ {
+ static::$globalVarCache = null;
+ }
}
diff --git a/modules/system/lang/lv/lang.php b/modules/system/lang/lv/lang.php
index fdd6a58638..f7e33a3e1b 100644
--- a/modules/system/lang/lv/lang.php
+++ b/modules/system/lang/lv/lang.php
@@ -191,7 +191,7 @@
'smtp_encryption_tls' => 'TLS',
'smtp_encryption_ssl' => 'SSL',
'sendmail' => 'Sendmail',
- 'sendmail_path' => 'Sendmail ceļs',
+ 'sendmail_path' => 'Sendmail ceļš',
'sendmail_path_comment' => 'Lūdzu, norādiet ceļu uz sendmail programmu.',
'drivers_hint_content' => 'Apskatiet dokumentāciju, lai uzzinātu vairāk par atbalstītajām pasta metodēm un kā tās ieslēgt.',
],
@@ -449,7 +449,7 @@
],
'media' => [
'invalid_path' => "Norādīts nederīgs faila ceļš: ':path'.",
- 'folder_size_items' => 'vienums(-i)',
+ 'folder_size_items' => 'vienumu|vienums|vienumi',
],
'page' => [
'custom_error' => [
diff --git a/modules/system/lang/ru/lang.php b/modules/system/lang/ru/lang.php
index ecd9b6bb94..f9f95a17bf 100644
--- a/modules/system/lang/ru/lang.php
+++ b/modules/system/lang/ru/lang.php
@@ -399,7 +399,7 @@
'return_link'=> 'Вернуться в журнал событий',
'id' => 'ID',
'id_label' => 'ID события',
- 'created_at' => 'Дата & Время',
+ 'created_at' => 'Дата и время',
'message'=> 'Сообщение',
'level'=> 'Уровень',
'preview_title'=> 'События',
diff --git a/modules/system/lang/ru/validation.php b/modules/system/lang/ru/validation.php
index b23ff54de5..0d766582bb 100644
--- a/modules/system/lang/ru/validation.php
+++ b/modules/system/lang/ru/validation.php
@@ -39,7 +39,7 @@
"digits_between" => "Длина цифрового поля :attribute должна быть между :min и :max.",
'dimensions' => ':attribute имеет недопустимые размеры изображения.',
'distinct' => 'Поле :attribute имеет повторяющееся значение.',
- "email" => "Поле :attribute имеет ошибочный формат.",
+ "email" => "Поле :attribute должно быть адресом электронной почты.",
'ends_with' => 'Поле :attribute должно заканчиваться одним из значений: :values.',
"exists" => "Выбранное значение для :attribute отсутствует.",
'file' => ':attribute должен быть файлом.',
diff --git a/modules/system/lang/tr/client.php b/modules/system/lang/tr/client.php
index 978f17e484..b362ab7c92 100644
--- a/modules/system/lang/tr/client.php
+++ b/modules/system/lang/tr/client.php
@@ -57,7 +57,10 @@
'weekdaysShort' => ['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt'],
],
'colorpicker' => [
- 'choose' => 'Seç',
+ 'last_color' => 'Önceden seçilen rengi kullan',
+ 'aria_palette' => 'Renk seçim alanı',
+ 'aria_hue' => 'Renk tonu seçimi',
+ 'aria_opacity' => 'Opaklık seçimi',
],
'filter' => [
'group' => [
diff --git a/modules/system/lang/tr/lang.php b/modules/system/lang/tr/lang.php
index ef2dbe8953..6b77d24f5a 100644
--- a/modules/system/lang/tr/lang.php
+++ b/modules/system/lang/tr/lang.php
@@ -43,6 +43,7 @@
'sv' => 'Svenska',
'sk' => 'Slovenský',
'sl' => 'Slovenščina',
+ 'th' => 'ไทย',
'tr' => 'Türkçe',
'uk' => 'Українська мова',
'zh-cn' => '简体中文',
@@ -55,11 +56,6 @@
'file' => [
'create_fail' => "Dosya oluşturulamıyor: :name",
],
- 'page' => [
- 'invalid_token' => [
- 'label' => 'Geçersiz güvenlik anahtarı',
- ],
- ],
'combiner' => [
'not_found' => "Kombine dosyası: ':name' bulunamadı.",
],
@@ -144,6 +140,9 @@
'refresh_success' => 'Eklentiler başarıyla yenilendi.',
'remove_confirm' => 'Seçili eklentileri kaldırmak istediğinize emin misiniz? Bu işlem, ilişkili tüm verileri de kaldıracaktır.',
'remove_success' => 'Eklentiler sistemden başarıyla kaldırıldı.',
+ 'replace' => [
+ 'multi_install_error' => 'Birden fazla eklentinin değiştirilmesi şu anda desteklenmiyor',
+ ],
],
'project' => [
'name' => 'Proje',
@@ -164,6 +163,9 @@
'not_found' => 'Belirtilen ayarlar bulunamadı.',
'missing_model' => 'Ayarlar sayfasında Model tanımı eksik.',
'update_success' => ':name için ayarlar güncellendi.',
+ 'test_subject' => 'Mail entegrasyon testi',
+ 'test_content' => 'Bu, e-mail ayarlarının çalıştığını doğrulamaya yönelik bir test e-postasıdır.',
+ 'test_confirm' => 'Bu, mevcut yapılandırmanızı kaydedecek ve :email adresine bir test e-postası gönderecektir.',
'return' => 'Sistem ayarları sayfasına dön',
'search' => 'Ara',
],
@@ -191,6 +193,7 @@
'sendmail' => 'Sendmail',
'sendmail_path' => 'Sendmail Yolu',
'sendmail_path_comment' => 'Sendmail programının yolunu belirtin.',
+ 'drivers_hint_content' => 'Desteklenen diğer email gönderim yöntemlerinin listesi ve bunların nasıl etkinleştirileceği için dökümanlara bakın.',
],
'mail_templates' => [
'menu_label' => 'Mail şablonları',
@@ -306,13 +309,21 @@
'plugin_description' => 'Açıklama',
'plugin_version' => 'Versiyon',
'plugin_author' => 'Yazar',
- 'plugin_not_found' => 'Plugin not found',
+ 'plugin_not_found' => 'Plugin bulunamadı',
+ 'plugin_version_not_found' => 'Plugin versiyonu bulunamadı',
'core_current_build' => 'Mevcut versiyon',
+ 'core_view_changelog' => 'Değişim Günlüğünü İncele',
'core_build' => 'Versiyon :build',
'core_build_help' => 'Son versiyon kullanılabilir.',
'core_downloading' => 'Uygulama dosyaları indiriliyor',
'core_extracting' => 'Uygulama dosyaları çıkarılıyor',
'core_set_build' => 'Build numarası güncelleniyor',
+ 'update_warnings_title' => 'Bazı sorunlar tespit edildi ve ilgilenilmesi gerekiyor:',
+ 'update_warnings_plugin_missing' => ':parent_code eklentisi çalışmadan önce :code un kurulu olması gerekiyor.',
+ 'update_warnings_plugin_replace' => ':plugin eklentisi, :alias ın yerine geçer, çakışma olmaması için lütfen :alias ı kaldırın.',
+ 'update_warnings_plugin_replace_cli' => 'Bu eklenti :alias ın yerine geçer, çakışma olmaması için lütfen :alias ı kaldırın.',
+ 'changelog' => 'Değişim Günlüğünü',
+ 'changelog_view_details' => 'Detaylar',
'plugins' => 'Modüller',
'themes' => 'Temalar',
'disabled' => 'Devre dışı',
@@ -417,8 +428,10 @@
'manage_mail_templates' => 'E-posta şablonları yönetebilir',
'manage_mail_settings' => 'E-posta ayarlarını yönetebilir',
'manage_other_administrators' => 'Diğer yöneticileri düzenleyebilir',
+ 'impersonate_users' => 'Kullanıcıların kimliğine bürün',
'manage_preferences' => 'Yönetim paneli seçeneklerini düzenleyebilir',
'manage_editor' => 'Kod editör ayarlarını düzenleyebilir',
+ 'manage_own_editor' => 'Kendine özel kod editör ayarlarını düzenleyebilir',
'view_the_dashboard' => 'Panoyu görüntüleyebilir',
'manage_default_dashboard' => 'Varsayılan kontrol panelini yönetebilir',
'manage_branding' => 'Yönetim Panelini özelleştirebilir',
@@ -438,4 +451,23 @@
'invalid_path' => "Geçersiz dosya dizini belirtildi: ':path'.",
'folder_size_items' => 'öğe(ler)',
],
+ 'page' => [
+ 'custom_error' => [
+ 'label' => 'Sayfa hatası',
+ 'help' => 'Üzgünüz ama bir şeyler ters gitti ve sayfa görüntülenemiyor.',
+ ],
+ 'invalid_token' => [
+ 'label' => 'Geçersiz güvenlik anahtarı',
+ ],
+ 'maintenance' => [
+ 'label' => "Hemen geri döneceğiz!",
+ 'help' => "Şu anda bakım aşamasındayız, kısa süre sonra tekrar kontrol edin!",
+ 'message' => 'Mesaj:',
+ 'available_at' => 'Şu süreden sonra tekrar deneyin:',
+ ],
+ ],
+ 'pagination' => [
+ 'previous' => 'Önceki',
+ 'next' => 'Sonraki',
+ ],
];
diff --git a/modules/system/lang/tr/validation.php b/modules/system/lang/tr/validation.php
index 00a4942b79..0d3584e8bc 100644
--- a/modules/system/lang/tr/validation.php
+++ b/modules/system/lang/tr/validation.php
@@ -13,17 +13,17 @@
|
*/
- 'accepted' => ':attribute kabul edilmelidir.',
- 'active_url' => ':attribute geçerli bir URL olmalıdır.',
- 'after' => ':attribute şundan eski bir tarih olmalıdır :date.',
- 'after_or_equal' => ':attribute şundan eski veya aynı bir tarih olmalıdır :date.',
- 'alpha' => ':attribute sadece harflerden oluşmalıdır.',
- 'alpha_dash' => ':attribute sadece harfler, rakamlar ve tirelerden oluşmalıdır.',
- 'alpha_num' => ':attribute sadece harfler ve rakamlar içermelidir.',
- 'array' => ':attribute dizi olmalıdır.',
- 'before' => ':attribute şundan önceki bir tarih olmalıdır :date.',
- 'before_or_equal' => ':attribute şundan önceki veya aynı bir tarih olmalıdır :date.',
- 'between' => [
+ 'accepted' => ':attribute kabul edilmelidir.',
+ 'active_url' => ':attribute geçerli bir URL olmalıdır.',
+ 'after' => ':attribute şundan eski bir tarih olmalıdır :date.',
+ 'after_or_equal' => ':attribute şundan eski veya aynı bir tarih olmalıdır :date.',
+ 'alpha' => ':attribute sadece harflerden oluşmalıdır.',
+ 'alpha_dash' => ':attribute sadece harfler, rakamlar ve tirelerden oluşmalıdır.',
+ 'alpha_num' => ':attribute sadece harfler ve rakamlar içermelidir.',
+ 'array' => ':attribute dizi olmalıdır.',
+ 'before' => ':attribute şundan önceki bir tarih olmalıdır :date.',
+ 'before_or_equal' => ':attribute şundan önceki veya aynı bir tarih olmalıdır :date.',
+ 'between' => [
'numeric' => ':attribute :min - :max arasında olmalıdır.',
'file' => ':attribute :min - :max arasındaki kilobayt değeri olmalıdır.',
'string' => ':attribute :min - :max arasında karakterden oluşmalıdır.',
@@ -32,6 +32,7 @@
'boolean' => ':attribute alanı true veya false olmalıdır.',
'confirmed' => ':attribute tekrarı eşleşmiyor.',
'date' => ':attribute geçerli bir tarih olmalıdır.',
+ 'date_equals' => ':attribute alanı :date ile eşit bir tarih olmalıdır.',
'date_format' => ':attribute :format biçimi ile eşleşmiyor.',
'different' => ':attribute ile :other birbirinden farklı olmalıdır.',
'digits' => ':attribute :digits rakam olmalıdır.',
@@ -39,9 +40,22 @@
'dimensions' => ':attribute geçersiz resim boyutlarına sahip.',
'distinct' => ':attribute alanı yinelenen bir değere sahip.',
'email' => ':attribute biçimi geçersiz.',
+ 'ends_with' => ':attribute şunlardan biri ile bitmeli: :values.',
'exists' => 'Seçili :attribute geçersiz.',
'file' => ':attribute bir dosya olmalı.',
'filled' => ':attribute alanın bir değeri içermelidir.',
+ 'gt' => [
+ 'numeric' => ':attribute, :value değerinden büyük olmalıdır.',
+ 'file' => ':attribute, :value kilobayttan büyük olmalıdır.',
+ 'string' => ':attribute, :value karakterden büyük olmalıdır.',
+ 'array' => ':attribute, :value öğeden daha fazla olmalıdır.',
+ ],
+ 'gte' => [
+ 'numeric' => ':attribute, :value değerinden büyük veya ona eşit olmalıdır.',
+ 'file' => ':attribute, :value kilobayttan büyük veya ona eşit olmalıdır.',
+ 'string' => ':attribute, :value karakterden büyük veya ona eşit olmalıdır.',
+ 'array' => ':attribute, :value öğeden veya daha fazlasına sahip olmalıdır.'
+ ],
'image' => ':attribute alanı resim dosyası olmalıdır.',
'in' => ':attribute değeri geçersiz.',
'in_array' => ':attribute alanı, :other da bulunmuyor.',
@@ -50,21 +64,34 @@
'ipv4' => ':attribute geçerli bir IPv4 adresi olmalıdır.',
'ipv6' => ':attribute geçerli bir IPv6 adresi olmalıdır.',
'json' => ':attribute geçerli bir JSON string olmalıdır.',
+ 'lt' => [
+ 'numeric' => ':attribute, :value değerinden küçük olmalıdır.',
+ 'file' => ':attribute, :value kilobayttan küçük olmalıdır.',
+ 'string' => ':attribute, :value karakterden küçük olmalıdır.',
+ 'array' => ':attribute, :value öğeden az olmalıdır.',
+ ],
+ 'lte' => [
+ 'numeric' => ':attribute, :value değerinden küçük veya ona eşit olmalıdır.',
+ 'file' => ':attribute, :value kilobayttan küçük veya ona eşit olmalıdır.',
+ 'string' => ':attribute, :value karakterden küçük veya ona eşit olmalıdır.',
+ 'array' => ':attribute, :value öğeden fazla olmamalıdır.',
+ ],
'max' => [
'numeric' => ':attribute değeri :max değerinden küçük olmalıdır.',
'file' => ':attribute değeri :max kilobayt değerinden küçük olmalıdır.',
'string' => ':attribute değeri :max karakter değerinden küçük olmalıdır.',
'array' => ':attribute değeri :max adedinden az nesneye sahip olmalıdır.',
],
- 'mimes' => ':attribute dosya biçimi :values olmalıdır.',
- 'mimetypes' => ':attribute dosya biçimi :values olmalıdır.',
- 'min' => [
+ 'mimes' => ':attribute dosya biçimi :values olmalıdır.',
+ 'mimetypes' => ':attribute dosya biçimi :values olmalıdır.',
+ 'min' => [
'numeric' => ':attribute değeri :min değerinden büyük olmalıdır.',
'file' => ':attribute değeri :min kilobayt değerinden büyük olmalıdır.',
'string' => ':attribute değeri :min karakter değerinden büyük olmalıdır.',
'array' => ':attribute en az :min nesneye sahip olmalıdır.',
],
'not_in' => 'Seçili :attribute geçersiz.',
+ 'not_regex' => ':attribute formatı geçersiz.',
'numeric' => ':attribute rakam olmalıdır.',
'present' => ':attribute alanı mevcut olmalı.',
'regex' => ':attribute biçimi geçersiz.',
@@ -82,11 +109,13 @@
'string' => ':attribute :size karakter olmalıdır.',
'array' => ':attribute :size nesneye sahip olmalıdır.',
],
- 'string' => 'The :attribute must be a string.',
- 'timezone' => 'The :attribute must be a valid zone.',
- 'unique' => ':attribute daha önceden kayıt edilmiş.',
- 'uploaded' => 'The :attribute failed to upload.',
- 'url' => ':attribute biçimi geçersiz.',
+ 'starts_with' => ':attribute şunlardan biriyle başlamalıdır: :values.',
+ 'string' => 'The :attribute must be a string.',
+ 'timezone' => 'The :attribute must be a valid zone.',
+ 'unique' => ':attribute daha önceden kayıt edilmiş.',
+ 'uploaded' => 'The :attribute failed to upload.',
+ 'url' => ':attribute biçimi geçersiz.',
+ 'uuid' => ':attribute geçerli bir UUID olmalıdır.',
/*
|--------------------------------------------------------------------------
diff --git a/modules/system/models/EventLog.php b/modules/system/models/EventLog.php
index 7836f17e87..f72c4f459c 100644
--- a/modules/system/models/EventLog.php
+++ b/modules/system/models/EventLog.php
@@ -1,9 +1,9 @@
message = $message;
@@ -66,10 +61,8 @@ public static function add($message, $level = 'info', $details = null)
/**
* Beautify level value.
- * @param string $level
- * @return string
*/
- public function getLevelAttribute($level)
+ public function getLevelAttribute(string $level): string
{
return ucfirst($level);
}
@@ -77,9 +70,8 @@ public function getLevelAttribute($level)
/**
* Creates a shorter version of the message attribute,
* extracts the exception message or limits by 100 characters.
- * @return string
*/
- public function getSummaryAttribute()
+ public function getSummaryAttribute(): string
{
if (preg_match("/with message '(.+)' in/", $this->message, $match)) {
return $match[1];
diff --git a/modules/system/tests/classes/FileManifestTest.php b/modules/system/tests/classes/FileManifestTest.php
index 4cf280f07a..2ceb55301c 100644
--- a/modules/system/tests/classes/FileManifestTest.php
+++ b/modules/system/tests/classes/FileManifestTest.php
@@ -2,6 +2,8 @@
namespace System\Tests\Classes;
+use ReflectionClass;
+
use System\Tests\Bootstrap\TestCase;
use Winter\Storm\Exception\ApplicationException;
use System\Classes\FileManifest;
@@ -11,14 +13,15 @@ class FileManifestTest extends TestCase
/** @var FileManifest instance */
protected $fileManifest;
+ /** @var root path */
+ protected $root;
+
public function setUp(): void
{
parent::setUp();
- $this->fileManifest = new FileManifest(
- base_path('modules/system/tests/fixtures/manifest/1_0_1'),
- ['test', 'test2']
- );
+ $this->root = base_path('modules/system/tests/fixtures/manifest/1_0_1');
+ $this->fileManifest = new FileManifest($this->root, ['test', 'test2']);
}
public function testGetFiles()
@@ -61,4 +64,15 @@ public function testSingleModule()
'test' => 'c0b794ff210862a4ce16223802efe6e28969f5a4fb42480ec8c2fef2da23d181',
], $this->fileManifest->getModuleChecksums());
}
+
+ public function testGetFilename()
+ {
+ $class = new ReflectionClass('System\Classes\FileManifest');
+ $method = $class->getMethod('getFilename');
+ $method->setAccessible(true);
+
+ $filename = '/modules/test/file1.php';
+
+ $this->assertEquals($filename, $method->invoke($this->fileManifest, $this->root . $filename));
+ }
}
diff --git a/modules/system/tests/console/CreateMigrationTest.php b/modules/system/tests/console/CreateMigrationTest.php
new file mode 100644
index 0000000000..77e30eea71
--- /dev/null
+++ b/modules/system/tests/console/CreateMigrationTest.php
@@ -0,0 +1,90 @@
+app->setPluginsPath(base_path() . '/modules/system/tests/fixtures/plugins/');
+
+ $this->table = 'winter_tester_test_model';
+ $this->versionFile = plugins_path('winter/tester/updates/version.yaml');
+ $this->versionFolder = plugins_path('winter/tester/updates/v0.0.1');
+
+ File::copy($this->versionFile, $this->versionFile . '.bak');
+ }
+
+ public function testCreateMigration()
+ {
+ $this->artisan('create:migration Winter.Tester -c --force --for-version v0.0.1 --model TestModel --name create_table');
+ $this->assertFileExists($this->versionFolder . '/create_table.php');
+
+ $migration = require_once $this->versionFolder . '/create_table.php';
+ $migration->up();
+
+ $this->assertTrue(Schema::hasTable($this->table));
+
+ $columns = [
+ 'id' => ['type'=>'integer', 'index'=>'primary', 'required'=>true],
+ 'cb' => ['type'=>'boolean'],
+ 'switch' => ['type'=>'boolean'],
+ 'int' => ['type'=>'integer'],
+ 'uint' => ['type'=>'integer', 'required'=>true],
+ 'double' => ['type'=>'float'],
+ 'range' => ['type'=>'integer', 'required'=>true],
+ 'datetime' => ['type'=>'datetime'],
+ 'date' => ['type'=>'date', 'required'=>true],
+ 'time' => ['type'=>'time'],
+ 'md' => ['type'=>'text'],
+ 'textarea' => ['type'=>'text'],
+ 'text' => ['type'=>'string', 'required'=>true],
+ 'phone_id' => ['type'=>'integer', 'index'=>true, 'required'=>true],
+ 'user_id' => ['type'=>'integer', 'index'=>true, 'required'=>true],
+ 'data' => ['type'=>'text'],
+ 'sort_order' => ['type'=>'integer', 'index'=>true],
+ 'taggable_id' => ['type'=>'integer', 'index'=>'morphable_index'],
+ 'taggable_type' => ['type'=>'string', 'index'=>'morphable_index'],
+ 'created_at' => ['type'=>'datetime'],
+ 'updated_at' => ['type'=>'datetime'],
+ ];
+
+ $table = Schema::getConnection()->getDoctrineSchemaManager()->listTableDetails($this->table);
+
+ foreach ($columns as $name => $definition) {
+ $this->assertEquals(array_get($definition, 'type'), Schema::getColumnType($this->table, $name));
+
+ // assert an index has been created for the primary, morph and foreign keys
+ if ($indexName = array_get($definition, 'index')) {
+ if ($indexName === true) {
+ $indexName = sprintf("%s_%s_index", $this->table, $name);
+ }
+ $this->assertTrue($table->hasIndex($indexName));
+
+ if ($indexName === 'morphable_index') {
+ $index = $table->getIndex($indexName);
+ $this->assertTrue(in_array($name, $index->getColumns()));
+ }
+ }
+ $this->assertEquals(array_get($definition, 'required', false), $table->getColumn($name)->getNotnull());
+ }
+
+ $migration->down();
+ $this->assertFalse(Schema::hasTable($this->table));
+ }
+
+ public function tearDown(): void
+ {
+ File::move($this->versionFile . '.bak', $this->versionFile);
+ File::deleteDirectory($this->versionFolder);
+
+ parent::tearDown();
+ }
+}
diff --git a/modules/system/tests/fixtures/plugins/winter/tester/models/TestModel.php b/modules/system/tests/fixtures/plugins/winter/tester/models/TestModel.php
new file mode 100644
index 0000000000..f43919f1e4
--- /dev/null
+++ b/modules/system/tests/fixtures/plugins/winter/tester/models/TestModel.php
@@ -0,0 +1,39 @@
+ TestUser::class
+ ];
+
+ public $hasOne = [
+ 'phone' => TestPhone::class
+ ];
+
+ public $morphTo = [
+ 'taggable' => []
+ ];
+
+ public $rules = [
+ 'uint' => 'required',
+ 'range' => 'required|integer|between:1,10',
+ ];
+}
+
+class TestUser extends Model
+{
+}
+
+class TestPhone extends Model
+{
+}
diff --git a/modules/system/tests/fixtures/plugins/winter/tester/models/testmodel/fields.yaml b/modules/system/tests/fixtures/plugins/winter/tester/models/testmodel/fields.yaml
new file mode 100644
index 0000000000..944d5cc0c5
--- /dev/null
+++ b/modules/system/tests/fixtures/plugins/winter/tester/models/testmodel/fields.yaml
@@ -0,0 +1,57 @@
+fields:
+ cb:
+ label: Checkbox
+ type: checkbox
+
+ switch:
+ label: Switch
+ type: switch
+
+ int:
+ label: Integer
+ type: number
+ step: 1
+
+ uint:
+ label: Unsigned Integer
+ type: number
+ step: 1
+ min: 0
+
+ double:
+ label: Double
+ type: number
+
+ range:
+ label: Range
+ type: range
+ min: 1
+ max: 10
+
+ datetime:
+ label: Datetime
+ type: datepicker
+
+ date:
+ label: Date
+ type: datepicker
+ mode: date
+ required: true
+
+ time:
+ label: Time
+ type: datepicker
+ mode: time
+
+ md:
+ label: Markdown
+ type: markdown
+
+ textarea:
+ label: Textarea
+ type: textarea
+
+ text:
+ label: Text
+ required: true
+
diff --git a/modules/system/views/mail/layout-default.php b/modules/system/views/mail/layout-default.php
index 171fa62fc3..10e970326e 100644
--- a/modules/system/views/mail/layout-default.php
+++ b/modules/system/views/mail/layout-default.php
@@ -7,13 +7,12 @@
-
-
-
+
+
diff --git a/modules/system/views/mail/layout-system.php b/modules/system/views/mail/layout-system.php
index 7c6d1ef09c..0d67c3aa50 100644
--- a/modules/system/views/mail/layout-system.php
+++ b/modules/system/views/mail/layout-system.php
@@ -11,13 +11,12 @@
-
-
-
+
+
|