diff --git a/_build/data/transport.core.system_settings.php b/_build/data/transport.core.system_settings.php
index 7a54d35138d..458afd5bf34 100644
--- a/_build/data/transport.core.system_settings.php
+++ b/_build/data/transport.core.system_settings.php
@@ -2175,5 +2175,59 @@
'area' => 'static_elements',
'editedon' => null,
], '', true, true);
+$settings['mask_disabled_modal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_disabled_modal']->fromArray([
+ 'key' => 'mask_disabled_modal',
+ 'value' => false,
+ 'xtype' => 'combo-boolean',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
+$settings['mask_color_modal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_color_modal']->fromArray([
+ 'key' => 'mask_color_modal',
+ 'value' => '#ffffff',
+ 'xtype' => 'textfield',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
+$settings['mask_opacity_modal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_opacity_modal']->fromArray([
+ 'key' => 'mask_opacity_modal',
+ 'value' => 50,
+ 'xtype' => 'numberfield',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
+$settings['mask_disabled_pseudomodal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_disabled_pseudomodal']->fromArray([
+ 'key' => 'mask_disabled_pseudomodal',
+ 'value' => false,
+ 'xtype' => 'combo-boolean',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
+$settings['mask_color_pseudomodal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_color_pseudomodal']->fromArray([
+ 'key' => 'mask_color_pseudomodal',
+ 'value' => '#0d141d',
+ 'xtype' => 'textfield',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
+$settings['mask_opacity_pseudomodal'] = $xpdo->newObject(modSystemSetting::class);
+$settings['mask_opacity_pseudomodal']->fromArray([
+ 'key' => 'mask_opacity_pseudomodal',
+ 'value' => 50,
+ 'xtype' => 'numberfield',
+ 'namespace' => 'core',
+ 'area' => 'manager',
+ 'editedon' => null,
+], '', true, true);
return $settings;
diff --git a/_build/templates/default/sass/_breakpoint-medium.scss b/_build/templates/default/sass/_breakpoint-medium.scss
new file mode 100644
index 00000000000..904e7d8c1b3
--- /dev/null
+++ b/_build/templates/default/sass/_breakpoint-medium.scss
@@ -0,0 +1,47 @@
+/* Medium screens, including small desktops and tablets */
+
+// Breakpoint for up to 1024px
+@include grid-media($tabletM) {
+
+}
+
+// Breakpoint for up to 960px
+@include grid-media($desktop) {
+ .x-window {
+ form {
+ .x-column-inner {
+ width: 100% !important;
+ }
+ .x-panel-body {
+ width: 100% !important;
+ }
+ }
+ .x-window-bc {
+ .x-window-footer {
+ padding: 15px;
+ }
+ }
+ &.qce-create,
+ &.qce-update {
+ .x-toolbar-cell {
+ margin-bottom: .75rem;
+ display: inline-block;
+ margin-right: 2%;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+ &.qce-create {
+ .x-toolbar-cell {
+ width: 49%;
+ }
+ }
+ &.qce-update {
+ .x-toolbar-cell {
+ width: 32%;
+ }
+ }
+ }
+
+}
diff --git a/_build/templates/default/sass/_breakpoint-small.scss b/_build/templates/default/sass/_breakpoint-small.scss
new file mode 100644
index 00000000000..f1d6f73ec7a
--- /dev/null
+++ b/_build/templates/default/sass/_breakpoint-small.scss
@@ -0,0 +1,20 @@
+// Breakpoint for up to tablet (portrait orientation) size 768px
+@include grid-media($tabletP) {
+
+}
+
+// Breakpoint for mobile size
+@include grid-media($mobile) {
+
+ .x-window {
+ &.qce-create,
+ &.qce-update {
+ .x-toolbar-cell {
+ display: block;
+ margin-right: 0;
+ width: 100%;
+ }
+ }
+ }
+
+}
diff --git a/_build/templates/default/sass/_colors-and-vars.scss b/_build/templates/default/sass/_colors-and-vars.scss
index fa3d6ad513f..92212d45672 100644
--- a/_build/templates/default/sass/_colors-and-vars.scss
+++ b/_build/templates/default/sass/_colors-and-vars.scss
@@ -3,6 +3,7 @@ $colorSplash: #234368;
$colorSplashLight: lighten($colorSplash, 50%);
$colorSplashMedium: lighten($colorSplash, 75%);
$colorSplashDark: darken($colorSplash, 20%);
+$colorSplashShadow: scale-color($colorSplash, $lightness: -70%, $saturation: -25%);
$colorSplashContrast: #FFFFFF; // needs much more adaption, should be used as text color for elements with $colorSplash background
$silver: #CCCCCC;
$gallery: #EEEEEE;
@@ -64,7 +65,7 @@ $borderRadius: 3px;
// Shadows
$boxShadow: 0 4px 6px rgba(0, 0, 0, 0.15);
-$boxShadowBig: 0 0 15px 0 rgba(0,0,0,0.2);
+$boxShadowBig: 0 0 15px 0 rgba($black,0.25);
$shadowBorder: 0 0 0 1px $borderColor;
$shadowBorderField: 0 0 0 1px $borderColor;
$shadowBorderDark: 0 0 0 1px $softGray;
diff --git a/_build/templates/default/sass/_forms.scss b/_build/templates/default/sass/_forms.scss
index 77c9e97b32c..fe3dc13ac83 100644
--- a/_build/templates/default/sass/_forms.scss
+++ b/_build/templates/default/sass/_forms.scss
@@ -76,7 +76,7 @@ textarea.x-form-field,
border-radius: $borderRadius;
border: 1px solid $borderColor;
position: relative;
- transition: border-color .25s;
+ transition: border-color 0.25s;
}
.x-viewport .x-trigger-wrap-focus,
@@ -142,7 +142,7 @@ input::-moz-focus-inner {
padding: 0 0 0 3px;
top: 0;
right: 0;
- transition: all .25s;
+ transition: all 0.25s;
width: 16px;
height: 16px;
@@ -150,7 +150,9 @@ input::-moz-focus-inner {
@extend %pseudo-font;
box-sizing: border-box;
color: scale-color($coreFieldLabelColor, $lightness: 50%);
- content: fa-content($fa-var-undo-alt); /* better match IMO for the action being taken */
+ content: fa-content(
+ $fa-var-undo-alt
+ ); /* better match IMO for the action being taken */
font-size: 14px;
position: relative;
bottom: 2px;
@@ -161,12 +163,12 @@ input::-moz-focus-inner {
height: 16px;
}
&.modx-field-reset {
- &::before {
- content: fa-content($fa-var-undo-alt);
- }
- &:hover::before {
- color: $green;
- }
+ &::before {
+ content: fa-content($fa-var-undo-alt);
+ }
+ &:hover::before {
+ color: $green;
+ }
}
&.modx-field-clear {
&::before {
@@ -242,19 +244,21 @@ input::-moz-focus-inner {
border-style: solid;
border-width: 10px 10px 10px 0;
border-color: transparent $lightGray transparent transparent;
- content: '';
+ content: "";
position: absolute;
top: 0;
left: -10px;
- transform: rotate(360deg); /* for better anti-aliasing in webkit browsers */
+ transform: rotate(
+ 360deg
+ ); /* for better anti-aliasing in webkit browsers */
width: 0;
height: 0;
}
&:after {
background-color: $white;
- border-radius: 50%; /* make a circle */
- content: '';
+ border-radius: 50%; /* make a circle */
+ content: "";
position: absolute;
top: 8px;
left: -4px;
@@ -276,7 +280,8 @@ input::-moz-focus-inner {
background-color: darken($colorSplash, 6%);
&:before {
- border-color: transparent darken($colorSplash, 6%) transparent transparent;
+ border-color: transparent darken($colorSplash, 6%) transparent
+ transparent;
}
}
}
@@ -288,7 +293,7 @@ input::-moz-focus-inner {
border: 1px solid $borderColor;
border-radius: $borderRadius;
padding: 5px;
- transition: all .25s;
+ transition: all 0.25s;
&:focus {
border: 1px solid $borderColorFocus;
@@ -332,7 +337,6 @@ input::-moz-focus-inner {
}
.x-window & {
-
.x-form-item-label {
padding: 10px 0 4px 0; /* move the form fields a bit tighter together inside windows */
}
@@ -352,21 +356,25 @@ input::-moz-focus-inner {
&.disabled {
label {
- color: scale-color($coreFieldLabelColor, $lightness: 50%);
+ color: scale-color($coreFieldLabelColor, $lightness: 50%);
}
}
.x-form-element {
padding: 0;
font: $baseText;
+ /* add margin to element without label, primarily for checkboxes/radios appearing after a regular field or help element */
+ &.add-label-space {
+ margin-top: 28px;
+ }
.x-form-invalid-icon {
- color: $red;
- &::before {
- @extend %pseudo-font;
- content: fa-content($fa-var-exclamation-triangle); /* : "\f071" */
- position: absolute;
- left: 3px;
- }
+ color: $red;
+ &::before {
+ @extend %pseudo-font;
+ content: fa-content($fa-var-exclamation-triangle); /* : "\f071" */
+ position: absolute;
+ left: 3px;
+ }
}
}
@@ -374,8 +382,7 @@ input::-moz-focus-inner {
/* prevent columns used inside form elements to have too much spacing, some custom TV types need this */
.x-column-inner > .x-column {
-
- ~.x-column {
+ ~ .x-column {
margin-left: 5px;
}
@@ -409,7 +416,7 @@ input::-moz-focus-inner {
}
&.toggle-slider-above {
- margin: .3em 0;
+ margin: 0.3em 0;
padding-left: 3.9em;
}
@@ -421,15 +428,15 @@ input::-moz-focus-inner {
.example-list {
ul {
- margin: .4em 0;
+ margin: 0.4em 0;
li {
position: relative;
- margin-bottom: .25em;
+ margin-bottom: 0.25em;
padding-left: 1.25em;
&::before {
@extend %pseudo-font;
position: absolute;
- left: .2em;
+ left: 0.2em;
top: 0;
content: fa-content($fa-var-angle-double-right);
color: scale-color($mediumGray, $lightness: 20%);
@@ -439,7 +446,7 @@ input::-moz-focus-inner {
}
.example-input,
.copy-this {
- padding: 0 .3em;
+ padding: 0 0.3em;
border-radius: 2px;
transition: width 1s;
}
@@ -477,7 +484,14 @@ input::-moz-focus-inner {
}
}
}
+ &:active {
+ color: $darkGray;
+ &::after {
+ color: $darkGray;
+ }
+ }
}
+
.feedback {
margin-left: 1.4rem;
color: scale-color($blue, $lightness: -35%);
@@ -500,13 +514,12 @@ input::-moz-focus-inner {
.deemphasize {
font-style: normal;
}
-
}
.fs-toggle {
padding-top: 1em;
margin-top: 2em;
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
border-top: 1px dashed $borderColor;
}
@@ -565,7 +578,6 @@ input::-moz-focus-inner {
}
}
}
-
}
.x-form-field {
@@ -635,7 +647,7 @@ input::-moz-focus-inner {
transform: translate(-50%, -50%);
text-align: center;
width: 30px;
- transition: opacity .25s;
+ transition: opacity 0.25s;
}
&.x-form-trigger-over,
@@ -681,7 +693,6 @@ input::-moz-focus-inner {
content: fa-content($fa-var-file-code);
font-weight: 400;
}
-
}
&.x-datetime-wrap {
@@ -815,7 +826,7 @@ input::-moz-focus-inner {
padding-left: 3px;
&:before {
- content: '';
+ content: "";
}
}
@@ -823,7 +834,7 @@ input::-moz-focus-inner {
@extend %pseudo-font;
box-sizing: border-box;
- content: '';
+ content: "";
font-size: 18px;
padding-right: 3px;
position: absolute;
@@ -904,8 +915,7 @@ input::-moz-focus-inner {
}
}
-/* .x-form-check-wrap */
-/* Special checboxes for resources and tv configs */
+/* Switch-style checboxes for resources, tv configs, quick edit windows, etc */
#modx-chunk-tabs,
#modx-plugin-tabs,
#modx-resource-tabs .display-switch,
@@ -913,7 +923,8 @@ input::-moz-focus-inner {
#modx-template-tabs,
#modx-tv-tabs .display-switch,
#modx-tv-editor-tabs,
-.display-switch {
+#modx-window-configure-mask,
+.x-window-footer .x-panel-fbar .display-switch {
&.space-before {
margin-top: 0.75rem;
@@ -922,8 +933,7 @@ input::-moz-focus-inner {
.x-form-check-wrap,
.x-fieldset-checkbox-toggle legend,
.x-fieldset legend {
- [type="checkbox"]{
-
+ [type="checkbox"] {
position: absolute;
left: -9999px;
html[dir="rtl"] & {
@@ -931,11 +941,11 @@ input::-moz-focus-inner {
left: unset;
}
- &+.x-form-cb-label,
- &+.x-fieldset-header-text {
+ & + .x-form-cb-label,
+ & + .x-fieldset-header-text {
position: relative;
padding-left: 3.6em;
- padding-top: .2em;
+ padding-top: 0.2em;
margin-left: 0;
cursor: pointer;
box-sizing: border-box;
@@ -943,9 +953,9 @@ input::-moz-focus-inner {
&:before,
&:after {
- content: '';
+ content: "";
position: absolute;
- transition: all .2s ease;
+ transition: all 0.2s ease;
font-size: inherit;
}
@@ -962,7 +972,7 @@ input::-moz-focus-inner {
&:after {
left: 0.1em;
top: 0.8em;
- margin-top: -.65em;
+ margin-top: -0.65em;
height: 1.3em;
width: 1.3em;
border-radius: 50%;
@@ -972,9 +982,8 @@ input::-moz-focus-inner {
}
&:checked {
-
- &+.x-form-cb-label,
- &+.x-fieldset-header-text {
+ & + .x-form-cb-label,
+ & + .x-fieldset-header-text {
&:after {
left: 1.6em;
top: 0.8em;
@@ -988,9 +997,8 @@ input::-moz-focus-inner {
}
&.danger:checked {
-
- &+.x-form-cb-label,
- &+.x-fieldset-header-text {
+ & + .x-form-cb-label,
+ & + .x-fieldset-header-text {
&:before {
background-color: $red;
border-color: $red;
@@ -999,9 +1007,8 @@ input::-moz-focus-inner {
}
&.warning:checked {
-
- &+.x-form-cb-label,
- &+.x-fieldset-header-text {
+ & + .x-form-cb-label,
+ & + .x-fieldset-header-text {
&:before {
background-color: $orange;
border-color: $orange;
@@ -1016,6 +1023,17 @@ input::-moz-focus-inner {
padding-top: 1.6rem;
}
+.x-window-footer {
+ .cb-clear-cache {
+ .x-form-check-wrap {
+ label {
+ font-size: $bodyFontSize * .92;
+ font-weight: 500;
+ }
+ }
+ }
+}
+
.x-form-check-group,
.x-form-radio-group {
/*overflow: visible; /* do not cut off the bottom of the input elements */
@@ -1033,12 +1051,11 @@ input::-moz-focus-inner {
}
/* applies to new xcheckboxgroup custom checkbox group */
&.aggregated-group {
- padding-left: 1em;
- padding-right: 1em;
+ padding-left: 1em;
+ padding-right: 1em;
}
}
-
/* superboxselect / multi-select field */
.x-superboxselect {
height: auto !important; /* override the extjs default theme style of 18px */
@@ -1088,13 +1105,13 @@ input::-moz-focus-inner {
cursor: pointer;
display: inline-block; /*font-size: 1px;*/
outline: 0; /* fix firefox dotted outlines */
- opacity: .6;
+ opacity: 0.6;
filter: alpha(opacity=60); /* for IE <= 8 */
padding: 0;
position: absolute;
top: 0;
right: 0;
- transition: opacity .25s;
+ transition: opacity 0.25s;
width: 16px;
height: 100%;
@@ -1177,7 +1194,7 @@ input::-moz-focus-inner {
margin-bottom: 2px;
}
- input[type=text],
+ input[type="text"],
textarea {
background-color: $coreFieldBg;
background-image: none;
@@ -1187,7 +1204,7 @@ input::-moz-focus-inner {
width: 97%;
}
- input[type=text] {
+ input[type="text"] {
font-size: 13px;
height: 20px !important;
padding: 5px;
@@ -1225,12 +1242,13 @@ input::-moz-focus-inner {
}
.x-editor .x-form-check-wrap {
- background-color: $white
+ background-color: $white;
}
/* fix combo on grid editor bug */
.x-grid-editor .x-form-field-wrap {
- background: #f6f2f7 url($imgPath + 'modx-theme/form/combo-bck.png') repeat-x scroll 0 100%;
+ background: #f6f2f7 url($imgPath+"modx-theme/form/combo-bck.png") repeat-x
+ scroll 0 100%;
}
.x-grid-editor .x-form-field-wrap input {
@@ -1239,11 +1257,11 @@ input::-moz-focus-inner {
.x-grid-editor .x-form-field-wrap img {
background-color: $white;
- background-image: url($imgPath + 'modx-theme/form/trigger.png');
+ background-image: url($imgPath+"modx-theme/form/trigger.png");
}
.x-form-grow-sizer {
- font: $fontSmall;
+ font: $baseText;
}
.x-form-invalid-msg {
@@ -1269,7 +1287,6 @@ input::-moz-focus-inner {
.x-grid3 {
.x-small-editor {
-
.x-form-text,
.x-form-field-wrap {
font: $fontSmall;
@@ -1373,7 +1390,7 @@ input::-moz-focus-inner {
.x-btn {
padding: 1px;
- transition: color .25s;
+ transition: color 0.25s;
&.x-btn-over,
&:hover,
@@ -1388,7 +1405,7 @@ input::-moz-focus-inner {
&.x-item-disabled {
color: $buttonColor;
- opacity: .4;
+ opacity: 0.4;
}
button:before {
@@ -1432,7 +1449,11 @@ input::-moz-focus-inner {
}
/* the second text cell, "of X" */
- .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell {
+ .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell {
.xtb-text {
display: inline-block;
position: absolute;
@@ -1444,7 +1465,15 @@ input::-moz-focus-inner {
}
/* the last regular button >>, yes, I know it's ugly but tell that Microsoft and say thanks for IE8 =) */
- .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell {
+ .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell
+ + .x-toolbar-cell {
.x-btn {
margin-right: 0;
}
@@ -1453,13 +1482,13 @@ input::-moz-focus-inner {
/* the refresh button */
.x-toolbar-cell:last-child {
opacity: 0;
- transition: opacity .25s;
+ transition: opacity 0.25s;
.x-btn {
font-size: 12px;
line-height: 1;
margin: 0;
- opacity: .4;
+ opacity: 0.4;
padding: 0;
position: absolute;
bottom: 2px;
@@ -1501,7 +1530,7 @@ input::-moz-focus-inner {
}
.x-combo-list-hd {
- background-image: url($imgPath + 'modx-theme/layout/panel-title-light-bg.gif');
+ background-image: url($imgPath+"modx-theme/layout/panel-title-light-bg.gif");
border-bottom-color: #bcbcbc;
color: #464646;
}
@@ -1548,18 +1577,18 @@ input::-moz-focus-inner {
.x-date-mp-ybtn a.x-date-mp-prev,
.x-date-mp-ybtn a.x-date-mp-next {
display: inline-block;
- opacity: .6;
+ opacity: 0.6;
filter: alpha(opacity=60); /* for IE <= 8 */
margin: 0 auto;
position: relative;
- transition: opacity .25s;
+ transition: opacity 0.25s;
&:before {
@extend %pseudo-font;
box-sizing: border-box;
color: $colorSplash;
- content: '';
+ content: "";
font-size: 18px;
position: absolute;
top: 0;
@@ -1788,7 +1817,7 @@ td.x-date-mp-sep {
border-radius: $borderRadius;
background-color: $coreFieldBg;
border: 1px solid $borderColor;
- background: url('../images/tp-no-preview.png') no-repeat center center;
+ background: url("../images/tp-no-preview.png") no-repeat center center;
overflow: hidden;
.x-panel-bwrap,
@@ -1809,6 +1838,6 @@ td.x-date-mp-sep {
bottom: 0;
padding: 10px 20px;
color: #fff;
- background-color: rgba(0, 0, 0, .8);
+ background-color: rgba(0, 0, 0, 0.8);
}
}
diff --git a/_build/templates/default/sass/_windows.scss b/_build/templates/default/sass/_windows.scss
index 2b8ff87a67e..44ca0603897 100644
--- a/_build/templates/default/sass/_windows.scss
+++ b/_build/templates/default/sass/_windows.scss
@@ -268,6 +268,11 @@
}
}
}
+ &.qce-window {
+ .x-window-body {
+ padding-top: 0;
+ }
+ }
}
/* .x-window */
@@ -299,20 +304,23 @@
/* the window modal mask, but also the mask that covers a grid when reloading for example */
.ext-el-mask {
- background-color: $white;
+ background-color: $colorSplashShadow;
opacity: 0;
- filter: alpha(opacity=0); /* for IE <= 8 */
- transition: opacity .25s;
- /*z-index: 10;*/ /* this is handeled by extjs and set to 9000 on show */
+ transition: opacity 0.75s ease-in-out, background-color 0.25s;
+ z-index: 200;
+
+ &.pseudomodal {
+ pointer-events: none;
+ }
&.fade-in {
- opacity: .5;
- filter: alpha(opacity=50); /* for IE <= 8 */
+ // opacity: .5;
}
+ // This affects the grid mask
.x-masked & {
+ background-color: $white;
opacity: .5;
- filter: alpha(opacity=50); /* for IE <= 8 */
z-index: 9; /* extjs standard is 100, 10 prevents overlapping the topnav dropdowns */
}
}
diff --git a/_build/templates/default/sass/index.scss b/_build/templates/default/sass/index.scss
index 67a987c5d60..94eb659895d 100644
--- a/_build/templates/default/sass/index.scss
+++ b/_build/templates/default/sass/index.scss
@@ -2615,3 +2615,6 @@ iframe[classname="x-hidden"] {
margin-top: 0 !important;
}
}
+
+@import "breakpoint-medium";
+@import "breakpoint-small";
diff --git a/core/lexicon/en/chunk.inc.php b/core/lexicon/en/chunk.inc.php
index 9ffddaf3127..3d975b1afb6 100644
--- a/core/lexicon/en/chunk.inc.php
+++ b/core/lexicon/en/chunk.inc.php
@@ -7,13 +7,8 @@
* @subpackage lexicon
*/
-// Entry out of alpha order because it must come before the entry it's used in below
-$_lang['example_tag_chunk_name'] = 'NameOfChunk';
-
$_lang['chunk'] = 'Chunk';
$_lang['chunk_category_desc'] = 'Use to group Chunks within the Elements tree.';
-$_lang['chunk_code'] = 'Chunk Code (HTML)';
-$_lang['chunk_description_desc'] = 'Usage information for this Chunk shown in search results and as a tooltip in the Elements tree.';
$_lang['chunk_delete_confirm'] = 'Are you sure you want to delete this chunk?';
$_lang['chunk_duplicate_confirm'] = 'Are you sure you want to duplicate this chunk?';
$_lang['chunk_err_create'] = 'An error occurred while trying to create the chunk.';
@@ -29,19 +24,30 @@
$_lang['chunk_err_ns_name'] = 'Please specify a name.';
$_lang['chunk_lock'] = 'Lock chunk for editing';
$_lang['chunk_lock_desc'] = 'Only users with “edit_locked” permissions can edit this Chunk.';
-$_lang['chunk_name_desc'] = 'Place the content generated by this Chunk in a Resource, Template, or other Chunk using the following MODX tag: [[+tag]]';
$_lang['chunk_new'] = 'Create Chunk';
$_lang['chunk_properties'] = 'Default Properties';
$_lang['chunk_tab_general_desc'] = 'Here you can enter the basic attributes for this Chunk as well as its content. The content must be HTML, either placed in the Chunk Code field below or in a static external file, and may include MODX tags. Note, however, that PHP code will not run in this element.';
-$_lang['chunk_tag_copied'] = 'Chunk tag copied!';
+$_lang['chunk_title'] = 'Create/edit chunk';
+$_lang['chunk_untitled'] = 'Untitled Chunk';
$_lang['chunks'] = 'Chunks';
// Temporarily match old keys to new ones to ensure compatibility
// --fields
$_lang['chunk_desc_category'] = $_lang['chunk_category_desc'];
-$_lang['chunk_desc_description'] = $_lang['chunk_description_desc'];
-$_lang['chunk_desc_name'] = $_lang['chunk_name_desc'];
$_lang['chunk_lock_msg'] = $_lang['chunk_lock_desc'];
// --tabs
$_lang['chunk_msg'] = $_lang['chunk_tab_general_desc'];
+
+/*
+ Refer to default.inc.php for the keys below.
+ (Placement in this default file necessary to allow
+ quick create/edit panels access to them when opened
+ outside the context of their respective element types)
+
+ example_tag_chunk_name
+ chunk_code
+ chunk_description_desc
+ chunk_name_desc
+ chunk_tag_copied
+*/
diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php
index 93a8896ba0b..2755e8919dd 100644
--- a/core/lexicon/en/default.inc.php
+++ b/core/lexicon/en/default.inc.php
@@ -46,7 +46,7 @@
$_lang['cache_sitepublishing_file_error'] = '
ERROR: Could not write site publishing file to cache.
';
$_lang['cache_unpublish_event_error'] = 'ERROR: Could not determine next unpublish event!
[[+info]]
';
$_lang['cached'] = 'Cached';
-$_lang['cancel'] = 'Close';
+$_lang['cancel'] = 'Cancel';
$_lang['caption'] = 'Caption';
$_lang['caption_desc'] = 'The name to show beside the input when editing a TV on a Resource form.';
$_lang['categories'] = 'Categories';
@@ -263,6 +263,23 @@
$_lang['manage_files'] = 'Manage Files';
$_lang['manager'] = 'Manager';
$_lang['manager_log_err_save'] = 'An error occurred while logging the manager action.';
+
+$_lang['mask_config_field_color'] = 'Mask Color';
+$_lang['mask_config_field_disabled'] = 'Disable Mask';
+$_lang['mask_config_field_disabled_desc'] = 'Remove the window backdrop to fully reveal the page below.';
+$_lang['mask_config_field_opacity'] = 'Mask Opacity';
+$_lang['mask_config_field_update_global'] = 'Update Global Settings';
+$_lang['mask_config_field_update_global_desc'] = 'Apply these changes to the global MODx settings.';
+$_lang['mask_config_field_update_user'] = 'Update User Settings';
+$_lang['mask_config_field_update_user_desc'] = 'Apply these changes to the current user’s settings. If switched off, changes made here will be temporary and lost upon logout.';
+$_lang['mask_config_window_title'] = 'Configure Mask';
+$_lang['mask_toolbar_tool_title'] = 'Mask Settings';
+$_lang['mask_toolbar_tool_qtip'] = 'Open mask configuration window';
+
+$_lang['mask_config_confirm_session_only'] = '';
+$_lang[''] = '';
+$_lang[''] = '';
+
$_lang['media'] = 'Media';
$_lang['menu_order'] = 'Menu Order';
$_lang['mime_type'] = 'MIME Type';
@@ -572,14 +589,68 @@
access when they are opened outside the
context of their respective element types
*/
+
// All
+$_lang['static_file'] = 'Static File';
+$_lang['static_file_desc'] = 'The external file location where the source code for this element is stored.';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['static_file_msg'] = $_lang['static_file_desc'];
+
+// Chunks
+$_lang['example_tag_chunk_name'] = 'NameOfChunk';
+$_lang['chunk_code'] = 'Chunk Code (HTML)';
+$_lang['chunk_description_desc'] = 'Usage information for this Chunk shown in search results and as a tooltip in the Elements tree.';
+$_lang['chunk_name_desc'] = 'Place the content generated by this Chunk in a Resource, Template, or other Chunk using the following MODX tag: [[+tag]]';
+$_lang['chunk_new_name'] = 'New Chunk Name';
+$_lang['chunk_tag_copied'] = 'Chunk tag copied!';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['chunk_desc_description'] = $_lang['chunk_description_desc'];
+ $_lang['chunk_desc_name'] = $_lang['chunk_name_desc'];
+
+// Plugins
+$_lang['plugin_code'] = 'Plugin Code (PHP)';
+$_lang['plugin_description_desc'] = 'Usage information for this Plugin shown in search results and as a tooltip in the Elements tree.';
+$_lang['plugin_disabled'] = 'Deactivate Plugin';
+$_lang['plugin_disabled_desc'] = 'When deactivated, this Plugin will not respond to events.';
+$_lang['plugin_new_name'] = 'New Plugin Name';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['plugin_desc'] = $_lang['description'];
+ $_lang['plugin_desc_description'] = $_lang['plugin_description_desc'];
+ $_lang['plugin_disabled_msg'] = $_lang['plugin_disabled_desc'];
+
+// Snippets
+$_lang['example_tag_snippet_name'] = 'NameOfSnippet';
+$_lang['snippet_code'] = 'Snippet Code (PHP)';
+$_lang['snippet_description_desc'] = 'Usage information for this Snippet shown in search results and as a tooltip in the Elements tree.';
+$_lang['snippet_name_desc'] = 'Place the content generated by this Snippet in a Resource, Template, or Chunk using the following MODX tag: [[+tag]]';
+$_lang['snippet_new_name'] = 'New Snippet Name';
+$_lang['snippet_tag_copied'] = 'Snippet tag copied!';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['snippet_desc'] = $_lang['description'];
+ $_lang['snippet_desc_description'] = $_lang['snippet_description_desc'];
+
+// Templates
+$_lang['template_code'] = 'Template Code (HTML)';
+$_lang['template_description_desc'] = 'Usage information for this Template shown in search results and as a tooltip in the Elements tree.';
+$_lang['template_new_name'] = 'New Template Name';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['template_desc'] = $_lang['description'];
+ $_lang['template_desc_description'] = $_lang['template_description_desc'];
// TVs
-$_lang['tv_type'] = 'Input Type';
-$_lang['tv_default'] = 'Default Value';
-$_lang['tv_default_desc'] = 'The content this TV will show if user-entered content is not provided.';
+$_lang['example_tag_tv_name'] = 'NameOfTV';
$_lang['tv_caption_desc'] = 'The label shown for this TV in Resource editing pages (can be overridden per template or other criteria using Form Customization).';
$_lang['tv_category_desc'] = 'Use to group TVs in Resource editing pages and within the Elements tree.';
-$_lang['tv_description_desc'] = 'Usage information for this TV shown next to its caption in Resource editing pages and as a tooltip in the Elements tree.';
+$_lang['tv_default'] = 'Default Value';
+$_lang['tv_default_desc'] = 'The content this TV will show if user-entered content is not provided.';
+$_lang['tv_description_desc'] = 'Usage information for this TV shown next to its caption in Resource editing pages, as a tooltip in the Elements tree, and within search results.';
$_lang['tv_elements'] = 'Input Option Values';
$_lang['tv_elements_short_desc'] = 'Defines the selectable options for this TV, which may be manually entered or built with a one-line database query.';
+$_lang['tv_name_desc'] = 'Place the content generated by this TV in a Resource, Template, or Chunk using the following MODX tag: [[+tag]]';
+$_lang['tv_new_caption'] = 'New TV Caption';
+$_lang['tv_new_name'] = 'New TV Name';
+$_lang['tv_tag_copied'] = 'TV tag copied!';
+$_lang['tv_type'] = 'Input Type';
+$_lang['tv_type_desc'] = 'The html input or content component type generated by this TV.';
+ // Temporarily match old keys to new ones to ensure compatibility
+ $_lang['tv_description'] = $_lang['description'];
diff --git a/core/lexicon/en/element.inc.php b/core/lexicon/en/element.inc.php
index d7ea047a1bc..e7613c40e39 100644
--- a/core/lexicon/en/element.inc.php
+++ b/core/lexicon/en/element.inc.php
@@ -27,8 +27,6 @@
$_lang['quick_update_tv'] = 'Quick Edit TV';
$_lang['property_preprocess'] = 'Pre-process tags in Property Values';
$_lang['property_preprocess_msg'] = 'If enabled, tags in Default Property/Property Set values will be processed before they are used for Element processing.';
-$_lang['static_file'] = 'Static File';
-$_lang['static_file_desc'] = 'The external file location where the source code for this element is stored.';
$_lang['static_source'] = 'Media Source';
$_lang['static_source_desc'] = 'Sets the basePath for the Static File to the one specified in the chosen Media Source. Choose “None” when specifying an absolute or other custom path to the file.';
$_lang['tv_elements'] = 'Input Option Values';
@@ -40,5 +38,15 @@
// Temporarily match old keys to new ones to ensure compatibility
$_lang['is_static_msg'] = $_lang['is_static_desc'];
-$_lang['static_file_msg'] = $_lang['static_file_desc'];
$_lang['static_source_msg'] = $_lang['static_source_desc'];
+
+/*
+ Refer to default.inc.php for the keys below.
+ (Placement in this default file necessary to allow
+ quick create/edit/duplicate panels access to them when opened
+ outside the context of their respective element types)
+
+ static_file
+ static_file_desc
+
+*/
diff --git a/core/lexicon/en/plugin.inc.php b/core/lexicon/en/plugin.inc.php
index a153e8841c9..e2eb313fd5e 100644
--- a/core/lexicon/en/plugin.inc.php
+++ b/core/lexicon/en/plugin.inc.php
@@ -11,12 +11,8 @@
$_lang['plugin'] = 'Plugin';
$_lang['plugin_add'] = 'Add Plugin';
$_lang['plugin_category_desc'] = 'Use to group Plugins within the Elements tree.';
-$_lang['plugin_code'] = 'Plugin Code (PHP)';
$_lang['plugin_config'] = 'Plugin configuration';
-$_lang['plugin_description_desc'] = 'Usage information for this Plugin shown in search results and as a tooltip in the Elements tree.';
$_lang['plugin_delete_confirm'] = 'Are you sure you want to delete this plugin?';
-$_lang['plugin_disabled'] = 'Deactivate Plugin';
-$_lang['plugin_disabled_msg'] = 'When deactivated, this Plugin will not respond to events.';
$_lang['plugin_duplicate_confirm'] = 'Are you sure you want to duplicate this plugin?';
$_lang['plugin_err_create'] = 'An error occurred while creating the plugin.';
$_lang['plugin_err_ae'] = 'A plugin already exists with the name "[[+name]]".';
@@ -48,9 +44,20 @@
// Temporarily match old keys to new ones to ensure compatibility
// --fields
$_lang['plugin_desc_category'] = $_lang['plugin_category_desc'];
-$_lang['plugin_desc_description'] = $_lang['plugin_description_desc'];
$_lang['plugin_desc_name'] = $_lang['plugin_name_desc'];
$_lang['plugin_lock_msg'] = $_lang['plugin_lock_desc'];
// --tabs
$_lang['plugin_msg'] = $_lang['plugin_tab_general_desc'];
+
+/*
+ Refer to default.inc.php for the keys below.
+ (Placement in this default file necessary to allow
+ quick create/edit panels access to them when opened
+ outside the context of their respective element types)
+
+ plugin_code
+ plugin_description_desc
+ plugin_disabled
+ plugin_disabled_desc
+*/
diff --git a/core/lexicon/en/setting.inc.php b/core/lexicon/en/setting.inc.php
index 11718d4dc07..b052e63035b 100644
--- a/core/lexicon/en/setting.inc.php
+++ b/core/lexicon/en/setting.inc.php
@@ -501,6 +501,24 @@
$_lang['setting_package_installer_at_top'] = 'Pin Package-Installer at top';
$_lang['setting_package_installer_at_top_desc'] = 'If enabled, the Installer entry will be pinned to the top of the Extras menu. Otherwise it will be positioned according to its menuindex.';
+$_lang['setting_mask_disabled_pseudomodal'] = 'Disable Window Masking';
+$_lang['setting_mask_disabled_pseudomodal_desc'] = 'Removes the semi-transparent backdrop that masks the manager’s main interface below one or more editing windows. This setting may also be managed directly from an editing window’s top tool bar.';
+
+$_lang['setting_mask_color_pseudomodal'] = 'Window Mask Color';
+$_lang['setting_mask_color_pseudomodal_desc'] = 'Any valid css color specified in Hexadecimal, RGB/A, HSL/A, W3C named, or transparent format. This setting may also be managed directly from an editing window’s top tool bar.';
+
+$_lang['setting_mask_opacity_pseudomodal'] = 'Window Mask Opacity';
+$_lang['setting_mask_opacity_pseudomodal_desc'] = 'Controls how opaque an editing window’s backdrop (mask) will be. Valid values range from 5 (more transparent) to 95 (more opaque). This setting may also be managed directly from an editing window’s top tool bar.';
+
+$_lang['setting_mask_disabled_modal'] = 'Disable Modal Masking';
+$_lang['setting_mask_disabled_modal_desc'] = 'Removes the semi-transparent backdrop that masks the manager’s main interface below dialog windows. When a modal is active, interaction with the main interface is blocked regardless of this setting’s value. This setting may also be managed directly from an editing window’s top tool bar.';
+
+$_lang['setting_mask_color_modal'] = 'Modal Mask Color';
+$_lang['setting_mask_color_modal_desc'] = 'Any valid css color specified in Hexadecimal, RGB/A, HSL/A, W3C named, or transparent format. This setting may also be managed directly from an editing window’s top tool bar.';
+
+$_lang['setting_mask_opacity_modal'] = 'Modal Mask Opacity';
+$_lang['setting_mask_opacity_modal_desc'] = 'Controls how opaque a dialog window’s backdrop (mask) will be. Valid values range from 5 (more transparent) to 95 (more opaque). This setting may also be managed directly from an editing window’s top tool bar.';
+
$_lang['setting_parser_recurse_uncacheable'] = 'Delay Uncacheable Parsing';
$_lang['setting_parser_recurse_uncacheable_desc'] = 'If disabled, uncacheable elements may have their output cached inside cacheable element content. Disable this ONLY if you are having problems with complex nested parsing which stopped working as expected.';
diff --git a/core/lexicon/en/snippet.inc.php b/core/lexicon/en/snippet.inc.php
index c06eb0cf1a1..ca6cfe8ddf3 100644
--- a/core/lexicon/en/snippet.inc.php
+++ b/core/lexicon/en/snippet.inc.php
@@ -6,13 +6,10 @@
* @package modx
* @subpackage lexicon
*/
-$_lang['example_tag_snippet_name'] = 'NameOfSnippet';
$_lang['snippet'] = 'Snippet';
$_lang['snippets_available'] = 'Snippets available for you to include in your page';
$_lang['snippet_category_desc'] = 'Use to group Snippets within the Elements tree.';
-$_lang['snippet_code'] = 'Snippet Code (PHP)';
$_lang['snippet_delete_confirm'] = 'Are you sure you want to delete this snippet?';
-$_lang['snippet_description_desc'] = 'Usage information for this Snippet shown in search results and as a tooltip in the Elements tree.';
$_lang['snippet_duplicate_confirm'] = 'Are you sure you want to duplicate this snippet?';
$_lang['snippet_duplicate_error'] = 'An error occurred while duplicating the snippet.';
$_lang['snippet_err_create'] = 'An error occurred while creating the snippet.';
@@ -30,19 +27,31 @@
$_lang['snippet_lock'] = 'Lock snippet for editing';
$_lang['snippet_lock_desc'] = 'Only users with “edit_locked” permissions can edit this Snippet.';
$_lang['snippet_management_msg'] = 'Here you can choose which snippet you wish to edit.';
-$_lang['snippet_name_desc'] = 'Place the content generated by this Snippet in a Resource, Template, or Chunk using the following MODX tag: [[+tag]]';
$_lang['snippet_new'] = 'Create Snippet';
$_lang['snippet_properties'] = 'Default Properties';
-$_lang['snippet_tab_general_desc'] = 'Here you can enter the basic attributes for this Snippet as well as its content. The content must be PHP, either placed in the Snippet Code field below or in a static external file. To receive output from your Snippet at the point where it is called (within a Template or Chunk), a value must be returned from within the code.';
-$_lang['snippet_tag_copied'] = 'Snippet tag copied!';
+$_lang['snippet_tab_general_desc'] = 'Here you can enter the basic attributes for this Snippet as well as its content. The content must be PHP, either placed in the Snippet Code field below or in a static external file. To receive output from your Snippet at the point where it is called (within a Template or Chunk), a value must be returned from within the code.';
+$_lang['snippet_title'] = 'Create/edit snippet';
+$_lang['snippet_untitled'] = 'Untitled snippet';
$_lang['snippets'] = 'Snippets';
// Temporarily match old keys to new ones to ensure compatibility
// --fields
$_lang['snippet_desc_category'] = $_lang['snippet_category_desc'];
-$_lang['snippet_desc_description'] = $_lang['snippet_description_desc'];
$_lang['snippet_desc_name'] = $_lang['snippet_name_desc'];
$_lang['snippet_lock_msg'] = $_lang['snippet_lock_desc'];
// --tabs
$_lang['snippet_msg'] = $_lang['snippet_tab_general_desc'];
+
+/*
+ Refer to default.inc.php for the keys below.
+ (Placement in this default file necessary to allow
+ quick create/edit panels access to them when opened
+ outside the context of their respective element types)
+
+ example_tag_snippet_name
+ snippet_code
+ snippet_description_desc
+ snippet_name_desc
+ snippet_tag_copied
+*/
diff --git a/core/lexicon/en/template.inc.php b/core/lexicon/en/template.inc.php
index 738b49ea204..ce68978dc62 100644
--- a/core/lexicon/en/template.inc.php
+++ b/core/lexicon/en/template.inc.php
@@ -13,7 +13,6 @@
$_lang['template'] = 'Template';
$_lang['template_assignedtv_tab'] = 'Assigned TVs';
$_lang['template_category_desc'] = 'Use to group Templates within the Elements tree.';
-$_lang['template_code'] = 'Template Code (HTML)';
$_lang['template_delete_confirm'] = 'Are you sure you want to delete this template?';
$_lang['template_description_desc'] = 'Usage information for this Template shown in search results and as a tooltip in the Elements tree.';
$_lang['template_duplicate_confirm'] = 'Are you sure you want to duplicate this template?';
@@ -57,9 +56,20 @@
// Temporarily match old keys to new ones to ensure compatibility
// --fields
$_lang['template_desc_category'] = $_lang['template_category_desc'];
-$_lang['template_desc_description'] = $_lang['template_description_desc'];
$_lang['template_desc_name'] = $_lang['template_name_desc'];
+$_lang['template_icon_description'] = $_lang['template_icon_desc'];
$_lang['template_lock_msg'] = $_lang['template_lock_desc'];
+$_lang['template_preview_description'] = $_lang['template_preview_desc'];
// --tabs
$_lang['template_msg'] = $_lang['template_tab_general_desc'];
+
+/*
+ Refer to default.inc.php for the keys below.
+ (Placement in this default file necessary to allow
+ quick create/edit panels access to them when opened
+ outside the context of their respective element types)
+
+ template_code
+ template_description_desc
+ */
diff --git a/core/lexicon/en/tv.inc.php b/core/lexicon/en/tv.inc.php
index f668a4d0a20..3f14b786ead 100644
--- a/core/lexicon/en/tv.inc.php
+++ b/core/lexicon/en/tv.inc.php
@@ -6,7 +6,6 @@
* @package modx
* @subpackage lexicon
*/
-$_lang['example_tag_tv_name'] = 'NameOfTV';
$_lang['has_access'] = 'Has Access?';
$_lang['filter_by_category'] = 'Filter by Category...';
$_lang['rank'] = 'Rank';
@@ -15,12 +14,8 @@
$_lang['tvs'] = 'Template Variables';
$_lang['tv_binding_msg'] = 'This field supports data source bindings using the @ commands';
$_lang['tv_caption'] = 'Caption';
-$_lang['tv_caption_desc'] = 'The label shown for this TV in Resource editing pages (can be overridden per template or other criteria using Form Customization).';
-$_lang['tv_category_desc'] = 'Use to group TVs in Resource editing pages and within the Elements tree.';
$_lang['tv_change_template_msg'] = 'Changing this template will cause the page to reload the TVs, losing any unsaved changes.
Are you sure you want to change this template?';
$_lang['tv_delete_confirm'] = 'Are you sure you want to delete this TV?';
-$_lang['tv_description'] = 'Description';
-$_lang['tv_description_desc'] = 'Usage information for this TV shown next to its caption in Resource editing pages, as a tooltip in the Elements tree, and within search results.';
$_lang['tv_err_delete'] = 'An error occurred while trying to delete the TV.';
$_lang['tv_err_duplicate'] = 'An error occurred while trying to duplicate the TV.';
$_lang['tv_err_duplicate_templates'] = 'An error occurred while duplicating the TV templates.';
@@ -38,11 +33,11 @@
$_lang['tv_err_save'] = 'An error occurred while saving the TV.';
$_lang['tv_inuse'] = 'The following document(s) are currently using this TV. To continue with the delete operation click the Delete button otherwise click the Cancel button.';
$_lang['tv_inuse_template'] = 'The following template(s) are currently using this TV: [[+templates]].
Please detach the TV from the template(s) before deleting it.';
-$_lang['is_static_tv_desc'] = 'Use an external file to store the default value for this TV. This may be useful if the default value’s content is particularly lengthy.';
+$_lang['tv_isstatic_desc'] = 'Use an external file to store the default value for this TV. This may be useful if the default value’s content is particularly lengthy.';
$_lang['tv_lock'] = 'Restrict Editing';
$_lang['tv_lock_desc'] = 'Only users with “edit_locked” permissions can edit this TV.';
$_lang['tv_management_msg'] = 'Manage additional custom TVs for your documents.';
-$_lang['tv_name_desc'] = 'Place the content generated by this TV in a Resource, Template, or Chunk using the following MODX tag: [[+tag]]';
+$_lang['tv_name'] = 'TV Name';
$_lang['tv_new'] = 'Create TV';
$_lang['tv_novars'] = 'No TVs found';
$_lang['tv_properties'] = 'Default Properties';
@@ -58,7 +53,6 @@
$_lang['tv_tab_sources_desc'] = 'Here you can assign the Media Sources that are to be used for this TV in each specified Context. Double-click on the Source name in the grid to change it.';
$_lang['tv_tab_tmpl_access'] = 'Template Access';
$_lang['tv_tab_tmpl_access_desc'] = 'Select the templates that are allowed to access this TV.';
-$_lang['tv_tag_copied'] = 'TV tag copied!';
$_lang['tv_widget'] = 'Widget';
$_lang['tv_widget_prop'] = 'Widget Properties';
$_lang['tvd_err_remove'] = 'An error occurred while trying to delete the TV from the document.';
@@ -70,6 +64,7 @@
// Temporarily match old keys to new ones to ensure compatibility
// -- fields
+$_lang['is_static_tv_desc'] = $_lang['tv_isstatic_desc'];
$_lang['tv_desc_caption'] = $_lang['tv_caption_desc'];
$_lang['tv_desc_category'] = $_lang['tv_category_desc'];
$_lang['tv_desc_description'] = $_lang['tv_description_desc'];
@@ -97,5 +92,8 @@
tv_caption_desc
tv_category_desc
tv_description_desc
+ tv_name_desc
+ tv_tag_copied
+ tv_type_desc
*/
diff --git a/core/src/Revolution/Processors/Security/User/Setting/GetListIn.php b/core/src/Revolution/Processors/Security/User/Setting/GetListIn.php
new file mode 100644
index 00000000000..2b88c16daa6
--- /dev/null
+++ b/core/src/Revolution/Processors/Security/User/Setting/GetListIn.php
@@ -0,0 +1,52 @@
+setDefaultProperties(['user' => 0]);
+
+ return parent::initialize();
+ }
+
+ /**
+ * Filter by user and a mulitple-key query
+ * @return array
+ */
+ public function prepareCriteria()
+ {
+ $criteria = [];
+ $criteria[] = ['user' => (int)$this->getProperty('user')];
+
+ if ($keys = $this->getProperty('keys', '')) {
+ $keys = json_decode($keys);
+ $criteria[] = ['key:IN' => $keys];
+ }
+ return $criteria;
+ }
+}
diff --git a/core/src/Revolution/Processors/Security/User/Setting/Update.php b/core/src/Revolution/Processors/Security/User/Setting/Update.php
index 6f6305df3b4..75201aafa4e 100644
--- a/core/src/Revolution/Processors/Security/User/Setting/Update.php
+++ b/core/src/Revolution/Processors/Security/User/Setting/Update.php
@@ -30,8 +30,8 @@ class Update extends \MODX\Revolution\Processors\System\Settings\Update
* @return bool|string|null
*/
public function initialize()
- {
- $user = (int)$this->getProperty('fk', 0);
+ {
+ $user = (int)$this->getProperty('fk', $this->getProperty('user', 0));
if (!$user) {
return $this->modx->lexicon('user_err_ns');
}
@@ -53,6 +53,4 @@ public function initialize()
return true;
}
-
}
-
diff --git a/core/src/Revolution/Processors/System/ConfigJs.php b/core/src/Revolution/Processors/System/ConfigJs.php
index c93f83f0fa3..ab8b2f120c0 100644
--- a/core/src/Revolution/Processors/System/ConfigJs.php
+++ b/core/src/Revolution/Processors/System/ConfigJs.php
@@ -90,6 +90,7 @@ public function process()
$this->modx->_userConfig
),
'user' => $this->modx->user->get('id'),
+ 'user_usergroups' => $this->modx->user->getUserGroupIds(true),
'version' => $this->modx->version['full_version'],
'resource_classes' => $resourceClasses,
'resource_classes_drop' => $resourceClassesDrop,
diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php
index f7c2b788469..81fdd17d089 100644
--- a/core/src/Revolution/modUser.php
+++ b/core/src/Revolution/modUser.php
@@ -634,7 +634,7 @@ public function getResourceGroups($ctx = '')
}
/**
- * Gets all the User Group IDs of the groups this user belongs to.
+ * (DEPRECATED) Gets all the User Group IDs of the groups this user belongs to.
*
* @access public
* @return array An array of User Group IDs.
@@ -685,6 +685,38 @@ public function getPrimaryGroup()
return $userGroup;
}
+ /**
+ * Gets all the User Group IDs of the groups this user belongs to.
+ *
+ * @param bool $sortByRank Whether to return the results in ranked order
+ * @param string $sortDirection If sortByRank, will sort in the specified direction
+ * @return array An array of User Group IDs.
+ */
+ public function getUserGroupIds(bool $sortByRank = false, string $sortDirection = 'ASC') : array
+ {
+ $groups = [];
+ $id = $this->get('id') ? (string)$this->get('id') : '0';
+ if (isset($_SESSION["modx.user.{$id}.userGroups"]) && $this->xpdo->user->get('id') == $this->get('id')) {
+ $groups = $_SESSION["modx.user.{$id}.userGroups"];
+ } else {
+ $c = $this->xpdo->newQuery(modUserGroup::class);
+ $c->where(['`UserGroupMembers`.`member`' => $this->get('id')]);
+ if ($sortByRank) {
+ $c->sortby('`UserGroupMembers`.`rank`', $sortDirection);
+ }
+ $memberGroups = $this->xpdo->getCollectionGraph(modUserGroup::class, '{"UserGroupMembers":{}}', $c);
+ if ($memberGroups) {
+ /** @var modUserGroup $group */
+ foreach ($memberGroups as $group) {
+ $groups[] = $group->get('id');
+ }
+ }
+ $_SESSION["modx.user.{$id}.userGroups"] = $groups;
+ }
+
+ return $groups;
+ }
+
/**
* Gets all the User Group names of the groups this user belongs to.
*
diff --git a/manager/assets/modext/core/modx.js b/manager/assets/modext/core/modx.js
index bbdf81902b5..3de017efd5a 100644
--- a/manager/assets/modext/core/modx.js
+++ b/manager/assets/modext/core/modx.js
@@ -46,6 +46,18 @@ Ext.extend(MODx,Ext.Component,{
,expandHelp: true
,defaultState: []
+ /**
+ * Tracks our custom non click event blocking 'pseudo' modals; should contain
+ * an object for each currently open modal containing at minimum a reference to
+ * the modal window’s id (itemId).
+ */
+ ,openPseudoModals: []
+
+ /**
+ * An Ext.Element object containing the page mask created by pseudo modals.
+ */
+ ,mask: {}
+
,startup: function() {
this.initQuickTips();
this.initMarkRequiredFields();
@@ -360,11 +372,15 @@ Ext.extend(MODx,Ext.Component,{
,login_context: 'mgr'
}
,listeners: {
- 'success': {fn:function(r) {
- if (this.fireEvent('afterLogout',r)) {
- location.href = './';
- }
- },scope:this}
+ success: {
+ fn: function(r) {
+ MODx.maskConfig.destroySessionConfig();
+ if (this.fireEvent('afterLogout', r)) {
+ window.location.href = './';
+ }
+ },
+ scope: this
+ }
}
});
}
@@ -437,49 +453,37 @@ Ext.extend(MODx,Ext.Component,{
}
,getStaticElementsPath: function(name, category, type) {
- var path = MODx.config.static_elements_basepath,
- ext = '';
-
- if (category.length > 0) {
- category = category.replace(/[^\w\s-]/gi, "");
- category = category.replace(/\s/g, '-').toLowerCase();
- // Convert nested elements to nested directory structure.
- category = category.replace(/--/gi, '/');
- category = "/" + category + "/";
- } else {
- category = "/";
- }
+ let path = MODx.config.static_elements_basepath,
+ ext = '';
+ const htmlExtension = MODx.config.static_elements_html_extension || '.tpl';
+ // console.log('cat before: ',category);
+ category = category.length > 0 ? MODx.util.Format.staticElementPathFragment(category, true) : '/' ;
+ // console.log('cat after: ',category);
// Remove trailing slash.
- path = path.replace(/\/$/, "");
+ path = path.replace(/\/$/, '');
switch(type) {
- case "templates":
- ext = ".template" + (MODx.config.static_elements_html_extension || ".tpl");
+ case 'templates':
+ ext = `.template${htmlExtension}`;
break;
- case "tvs":
- ext = ".tv" + (MODx.config.static_elements_html_extension || ".tpl");
+ case 'tvs':
+ ext = `.tv${htmlExtension}`;
break;
- case "chunks":
- ext = ".chunk" + (MODx.config.static_elements_html_extension || ".tpl");
+ case 'chunks':
+ ext = `.chunk${htmlExtension}`;
break;
- case "snippets":
- ext = ".snippet.php";
+ case 'snippets':
+ ext = '.snippet.php';
break;
- case "plugins":
- ext = ".plugin.php";
+ case 'plugins':
+ ext = '.plugin.php';
break;
}
- // Remove special characters and spaces.
- name = name.replace(/[^\w\s-]/gi, '');
- name = name.replace(/\s/g, '-').toLowerCase();
-
- if (name.length > 0) {
- path += "/" + type + category + name + ext;
- } else {
- path += "/" + type + category;
- }
+ name = MODx.util.Format.staticElementPathFragment(name);
+ path += '/' + type + category;
+ path += name.length > 0 ? name + ext : '' ;
return path;
}
@@ -855,6 +859,663 @@ Ext.reg('modx-ajax',MODx.Ajax);
MODx = new MODx();
+/**
+ * Used to fetch and control window and modal backdrops, as well as grid masks.
+ * Note: This class is instantiated after the full MODx config has been loaded (currently in header.tpl)
+ * @param {Object} config
+ */
+MODx.MaskManager = function(config = {}) {
+ this.settingsKeys = {
+ modal: {
+ disabled: 'mask_disabled_modal',
+ color: 'mask_color_modal',
+ opacity: 'mask_opacity_modal'
+ },
+ pseudomodal: {
+ disabled: 'mask_disabled_pseudomodal',
+ color: 'mask_color_pseudomodal',
+ opacity: 'mask_opacity_pseudomodal'
+ }
+ };
+ this.settingsXtypes = {
+ disabled: 'combo-boolean',
+ color: 'textfield',
+ opacity: 'numberfield'
+ };
+ Ext.apply(config, {
+ attributes: {
+ modal: {
+ disabled: MODx.util.Types.castToBoolean(MODx.config.mask_disabled_modal),
+ color: MODx.config.mask_color_modal || '#ffffff',
+ opacity: parseInt(MODx.config.mask_opacity_modal) / 100 || 0.5
+ },
+ pseudomodal: {
+ disabled: MODx.util.Types.castToBoolean(MODx.config.mask_disabled_pseudomodal),
+ color: MODx.config.mask_color_pseudomodal || '#0d141d',
+ opacity: parseInt(MODx.config.mask_opacity_pseudomodal) / 100 || 0.5
+ },
+ grid: {
+ disabled: false,
+ color: MODx.config.mask_color_grid || '#ffffff',
+ opacity: parseInt(MODx.config.mask_opacity_grid) / 100 || 0.5
+ }
+ }
+ });
+ this.config = config;
+ MODx.MaskManager.superclass.constructor.call(this, config);
+ this.addEvents({
+ actionsReady: false,
+ actionsDone: false,
+ actionsFail: false
+ });
+ this.on({
+ actionsReady: function() {
+ // console.log('Continuing ... writing changes ... this:', this);
+ this.commitSettingsChanges();
+ },
+ actionsDone: function() {
+ // MODx.msg.status({
+ // title: 'Action Complete',
+ // message: 'Updates to your mask configuration settings were successful!'
+ // });
+ console.log('actionsDone :: this', this);
+ if (this.saveStatus) {
+ this.saveStatus.exit();
+ }
+ },
+ actionsFail: function(response) {
+ // MODx.msg.status({
+ // title: 'Action Complete',
+ // message: 'Updates to your mask configuration settings were successful!'
+ // });
+ console.log('actionsFail :: response', response);
+ if (this.saveStatus) {
+ this.saveStatus.exit();
+ }
+ },
+ /*
+ @ NEW SESSION:
+ - MODx.config will have all correct settings vals
+
+ @ GRID SETTINGS CHANGE:
+ If to User...
+ - This one's easy, as User has top precedence; update session and
+ cache with new value without additional checks
+ If to Usergroup...
+ - If no matching key exists for User
+ ? Is User in multiple groups (which takes precedence?)
+ Y - Check for key in higher precedence group, if any
+ N - Update session data and overrides obj
+ ? How to check for current User keys
+ 1 - Always via db query, OR
+ 2 - Query db at session start, add key(s) if any to
+ session LS history, then add/remove as needed on
+ change (session overrides obj, no db queries needed)
+
+ ## TRACK KEY EXISTENCE at User and Usergroup levels with overrides obj
+ overrides: {
+ user: ['key1', 'key2', ...],
+ groups: {
+ // obj keys correspond to usergroup id
+ 1: ['key1', 'key2', ...],
+ 3: ['key1', 'key2', ...],
+ ...
+ }
+ }
+ */
+ /**
+ * Fired after direct changes (via settings grids) to mask configuration
+ * are made. Triggers update of the mask session values as needed
+ * to ensure changes are immediately reflected in the UI (without reloading)
+ * @param {Object} response The post-save response data
+ */
+ syncSettingFromGrid: function(response) {
+ console.log('createSettingFromGrid :: response', response);
+ const { action, data, gridType } = response;
+ if (typeof data?.key?.length) {
+ const { type, attribute } = this.getMaskPropNamesFromKey(data.key);
+ console.log(`
+ syncSettingFromGrid ::
+ Action: ${action}
+ Mask type: ${type}
+ Mask attr: ${attribute}
+ Grid Type: ${gridType}
+ `);
+ if (type && attribute) {
+ /*
+ Note that for the session values, we want the decimal opacity
+ value (for direct use in css) instead of the whole number equivalent
+ which is used for the setting value itself
+ */
+ const
+ value = action === 'remove' ? this.getMaskAttribute(type, attribute, true) : data.value,
+ rawValue = attribute === 'opacity' && value > 1 ? value / 100 : value,
+ newValue = this.prepareSettingValue(data.xtype, rawValue)
+ ;
+ if (this.hasSessionConfig) {
+ const overridesData = {
+ action: action,
+ gridType: gridType,
+ key: data.key
+ };
+ switch(gridType) {
+ case 'user':
+ case 'system':
+ // do something
+ break;
+ case 'usergroup':
+ if (data?.group && MODx.config.user_usergroups.includes(data.group)) {
+ console.log(`Sync settings for usergroup ${data.group}`);
+ overridesData.groupId = data.group
+ }
+ break;
+ // no default
+ }
+ this.updateSessionConfig(type, {
+ [attribute]: newValue
+ }, true, overridesData);
+ }
+ /*
+ When enabling previously disabled mask via the settings grids,
+ the mask element will need to be created here to ensure it
+ appears on subsequent window openings (without reloading page)
+ */
+ /*
+ let mask = document.querySelector(`.ext-el-mask.${type}`);
+ if (!mask) {
+ const referenceEl = Ext.getBody().last();
+ // console.log('Trying to create mask before this el:', referenceEl);
+ mask = MODx.maskConfig.createMask(referenceEl, type);
+ }
+ if (attribute === 'color') {
+ mask.style.backgroundColor = newValue;
+ }
+ */
+ }
+ }
+ },
+ createSettingFromGrid: function(data) {
+ console.log('createSettingFromGrid :: data', data);
+ },
+
+ updateSettingFromGrid: function(data) {
+ console.log('updateSettingFromGrid :: data', data);
+ if (typeof data?.key?.length) {
+ const { type, attribute } = this.getMaskPropNamesFromKey(data.key);
+ console.log(`
+ updateSettingFromGrid ::
+ Mask type: ${type}
+ Mask attr: ${attribute}
+ `);
+ if (type && attribute) {
+ /*
+ Note that for the session values, we want the decimal opacity
+ value (for direct use in css) instead of the whole number equivalent
+ which is used for the setting value itself
+ */
+ const
+ rawValue = attribute === 'opacity' && data.value > 1 ? data.value / 100 : data.value,
+ newValue = this.prepareSettingValue(data.xtype, rawValue)
+ ;
+ if (this.hasSessionConfig) {
+ this.updateSessionConfig(type, {
+ [attribute]: newValue
+ });
+ }
+ /*
+ When enabling previously disabled mask via the settings grids,
+ the mask element will need to be created here to ensure it
+ appears on subsequent window openings (without reloading page)
+ */
+ let mask = document.querySelector(`.ext-el-mask.${type}`);
+ if (!mask) {
+ const referenceEl = Ext.getBody().last();
+ // console.log('Trying to create mask before this el:', referenceEl);
+ mask = MODx.maskConfig.createMask(referenceEl, type);
+ }
+ if (attribute === 'color') {
+ mask.style.backgroundColor = newValue;
+ }
+ }
+ }
+ },
+ /**
+ * Upon setting removal, updates the session mask config (if present)
+ * with the appropriate fallback value for the removed setting;
+ * @param {Object} data
+ */
+ removeSettingFromGrid: function(data) {
+ console.log('removeSettingFromGrid :: data', data);
+ const record = data.record;
+ if (typeof record?.key?.length) {
+ const { type, attribute } = this.getMaskPropNamesFromKey(record.key);
+ console.log(`
+ removeSettingFromGrid ::
+ Mask type: ${type}
+ Mask attr: ${attribute}
+ `);
+ if (type && attribute) {
+ /*
+ Note that for the session values, we want the decimal opacity
+ value (for direct use in css) instead of the whole number equivalent
+ which is used for the setting value itself
+ */
+ const
+ // rawValue = attribute === 'opacity' && data.value > 1 ? data.value / 100 : data.value,
+ value = this.getMaskAttribute(type, attribute, true),
+ rawValue = attribute === 'opacity' && value > 1 ? value / 100 : value,
+ newValue = this.prepareSettingValue(record.xtype, rawValue)
+ ;
+ if (this.hasSessionConfig) {
+ this.updateSessionConfig(type, {
+ [attribute]: newValue
+ });
+ }
+ /*
+ When enabling previously disabled mask via the settings grids,
+ the mask element will need to be created here to ensure it
+ appears on subsequent window openings (without reloading page)
+ */
+ let mask = document.querySelector(`.ext-el-mask.${type}`);
+ if (!mask) {
+ const referenceEl = Ext.getBody().last();
+ // console.log('Trying to create mask before this el:', referenceEl);
+ mask = MODx.maskConfig.createMask(referenceEl, type);
+ }
+ if (attribute === 'color') {
+ mask.style.backgroundColor = newValue;
+ }
+ }
+ }
+ }
+ });
+};
+Ext.extend(MODx.MaskManager, Ext.Component, {
+ sessionMaskKey: 'sessionMaskConfig',
+ cache: {},
+ hasSessionConfig: false,
+ saveStatus: null,
+ /**
+ *
+ * @param {Ext.Element} reference The element this mask should be inserted before
+ * @param {String} type The window type
+ * @param {String} event
+ * @param {*} returnMask
+ * @returns
+ */
+ createMask: function(reference, type = 'pseudomodal', event = 'render', returnMask = true) {
+ let ready;
+ // Note that window reference components will have an el property
+ // while other general elements will not
+ const insertBefore = reference?.el?.dom || reference.dom;
+ if (type === 'pseudomodal') {
+ ready = event === 'render'
+ ? MODx.openPseudoModals.length === 0
+ : MODx.openPseudoModals.length >= 1
+ ;
+ if (ready && MODx.util.isEmptyObject(MODx.mask)) {
+ MODx.mask = Ext.getBody().createChild({ cls: 'ext-el-mask pseudomodal' }, insertBefore);
+ MODx.mask.setStyle('background-color', MODx.maskConfig.getMaskAttribute('pseudomodal', 'color'));
+ MODx.mask.hide();
+ if (returnMask) {
+ return MODx.mask;
+ }
+ }
+ }
+ },
+ /**
+ * Get a mask's css value (or disabled status) based on its window type and attribute
+ * @param {String} type The window type (modal, pseudomodal)
+ * @param {String} attribute The mask attribute to get (color, opacity, disabled)
+ * @returns The current value of the requested attribute
+ */
+ getMaskAttribute: function(type, attribute, getFallback = false) {
+ const sessionBranch = getFallback ? 'fallback' : 'current' ;
+ // if (!getFallback && !MODx.util.isEmptyObject(this.cache)) {
+ // console.log(`Getting attr from cache (${sessionBranch} branch) ...`, this.cache);
+ // return this.cache.attributes[type][attribute];
+ // }
+ if (!MODx.util.isEmptyObject(this.cache)) {
+ console.log(`Getting attr from cache (${sessionBranch} branch) ...`, this.cache);
+ return this.cache[sessionBranch][type][attribute];
+ }
+ const sessionConfig = this.getSessionConfig();
+ if (!sessionConfig) {
+ console.log(`Getting ${type} ${attribute} from initial config: (${typeof this.attributes[type][attribute]}) ${this.attributes[type][attribute]}; MODx config val = (${typeof MODx.config[this.settingsKeys[type][attribute]]}) ${MODx.config[this.settingsKeys[type][attribute]]}`);
+ return this.attributes[type][attribute];
+ }
+ console.log(`Getting attr from session storage (${sessionBranch} branch) ...`, this.cache);
+ // return sessionConfig?.attributes[type][attribute];
+ return sessionConfig[sessionBranch][type][attribute];
+ },
+ createSessionConfig: function() {
+ console.log('Initial MODx config:', MODx.config);
+ const config = {
+ current: this.config.attributes,
+ fallback: this.config.attributes,
+ overrides: {
+ user: [],
+ groups: new Map()
+ }
+ };
+ MODx.config.user_usergroups.forEach(groupId => {
+ config.overrides.groups.set(groupId, []);
+ });
+
+ console.log('session config skel:', config);
+ this.saveSessionConfig(config);
+ this.hasSessionConfig = true;
+ },
+ saveSessionConfig: function(config) {
+ this.cache = config;
+ localStorage.setItem(this.sessionMaskKey, JSON.stringify(config, MODx.util.JsonTools.mapReplacer));
+ },
+ getSessionConfig: function() {
+ let sessionConfig = localStorage.getItem(this.sessionMaskKey);
+ if (!sessionConfig) {
+ this.hasSessionConfig = false;
+ return false;
+ }
+ sessionConfig = JSON.parse(sessionConfig, MODx.util.JsonTools.mapReviver);
+ if (MODx.util.isEmptyObject(this.cache)) {
+ this.cache = sessionConfig;
+ }
+ this.hasSessionConfig = true;
+ return sessionConfig;
+ },
+ destroySessionConfig: function() {
+ localStorage.removeItem(this.sessionMaskKey);
+ },
+ clearSessionConfig: function() {
+ localStorage.removeItem(this.sessionMaskKey);
+ this.hasSessionConfig = false;
+ },
+ updateSessionConfig: function(type, config, updateFallback = false, overridesData = null) {
+ let sessionConfig = this.getSessionConfig();
+ if (!sessionConfig) {
+ sessionConfig = this.config;
+ }
+ Object.keys(config).forEach(key => {
+ // console.log(`Updating ${type} key (${key} to ${config[key]})`);
+ // sessionConfig.attributes[type][key] = config[key];
+ sessionConfig.current[type][key] = config[key];
+ if (updateFallback) {
+ sessionConfig.fallback[type][key] = config[key];
+ }
+ });
+ if (overridesData) {
+ const
+ keyList = sessionConfig.overrides.groups.get(overridesData.groupId),
+ keyListHasKey = keyList.includes(overridesData.key)
+ ;
+ // console.log(`Override keyList for group ${overridesData.groupId}`, keyList);
+ if (overridesData.action === 'remove' && keyListHasKey) {
+ keyList = keyList.filter(key => key !== overridesData.key);
+ } else if (['create', 'update'].includes(overridesData.action) && !keyListHasKey) {
+ keyList.push(overridesData.key);
+ }
+ sessionConfig.overrides.groups.get(overridesData.groupId, keyList);
+ }
+ // this.cache = sessionConfig;
+ // localStorage.setItem(this.sessionMaskKey, JSON.stringify(sessionConfig));
+ // this.hasSessionConfig = true;
+ this.saveSessionConfig(sessionConfig);
+ },
+ /**
+ * Get the mask type and attribute prop names based on a settings key
+ * @param {String} queryKey The settings key being processed
+ * @returns {Object}
+ */
+ getMaskPropNamesFromKey: function(queryKey) {
+ const props = {
+ type: null,
+ attribute: null
+ };
+ for (const maskType in this.settingsKeys) {
+ const result = Object.keys(this.settingsKeys[maskType]).find(key => this.settingsKeys[maskType][key] === queryKey);
+ if (result) {
+ props.type = maskType;
+ props.attribute = result;
+ break;
+ }
+ }
+ return props;
+ },
+ /**
+ * Prepare global/user setting values for comparison to form values
+ * and/or for updating the session configuration
+ * @param {String} xtype The Ext xtype for the setting's editor
+ * @param {Boolean} initialValue Current setting value retrieved from config or database
+ */
+ prepareSettingValue: function(xtype, initialValue = null) {
+ let value = initialValue;
+ if (xtype.includes('number')) {
+ value = parseFloat(value);
+ } else if (xtype.includes('boolean')) {
+ value = MODx.util.Types.castToBoolean(value);
+ }
+ return value;
+ },
+ updateSystemSettings: function(windowType, settingsTarget, values, userId) {
+ const
+ params = {
+ namespace: 'core',
+ area: 'manager'
+ },
+ exitDelay = 150,
+ /**
+ *
+ */
+ SetActionMap = target => {
+ const
+ currentSettings = {
+ user: {},
+ global: {}
+ },
+ buildMap = (target, settings) => {
+ this.settingsMap.keys.forEach(key => {
+ const
+ userSettingExists = Object.hasOwn(settings.user, key),
+ globalSettingExists = Object.hasOwn(settings.global, key),
+ userSettingSaveAction = userSettingExists ? 'update' : 'create',
+ globalSettingSaveAction = globalSettingExists ? 'update' : 'create',
+ payload = {
+ ...params,
+ key: key,
+ value: this.valuesMap[key],
+ xtype: this.settingsMap.xtypes[key],
+ status: 0
+ }
+ ;
+ if (target === 'user') {
+ // Remove setting if it matches the global setting
+ if (userSettingExists && settings.global[key] === this.valuesMap[key]) {
+ this.actionMap.user.delete.push({
+ key: key,
+ user: MODx.config.user,
+ status: 0
+ });
+ this.actionMap.totalActions++;
+ // Create or update otherwise
+ } else if (
+ (!userSettingExists && settings.global[key] !== this.valuesMap[key])
+ || (userSettingExists && settings.user[key] !== this.valuesMap[key])
+ ) {
+ this.actionMap.user[userSettingSaveAction].push({
+ ...payload,
+ user: MODx.config.user,
+ });
+ this.actionMap.totalActions++;
+ }
+ }
+ // Remove user settings since, in this case, they would match the global one being updated
+ if (target === 'both' && userSettingExists) {
+ this.actionMap.user.delete.push({
+ key: key,
+ user: MODx.config.user,
+ status: 0
+ });
+ this.actionMap.totalActions++;
+ }
+ // Handle global settings for all targets; note that we elect to re-create the global key/value if it's missing
+ if (!globalSettingExists || (['both', 'global'].includes(target) && settings.global[key] !== this.valuesMap[key])) {
+ this.actionMap.global[globalSettingSaveAction].push(payload);
+ this.actionMap.totalActions++;
+ }
+ });
+
+ }
+ ;
+ this.settingsMap.keys.forEach(key => {
+ currentSettings.global[key] = this.prepareSettingValue(this.settingsMap.xtypes[key], MODx.config[key]);
+ });
+
+ // Fetch user settings to determine which ones are present and can be acted upon
+ MODx.Ajax.request({
+ url: MODx.config.connector_url,
+ params: {
+ ...params,
+ action: 'Security/User/Setting/GetListIn',
+ user: MODx.config.user,
+ keys: JSON.stringify(this.settingsMap.keys)
+ },
+ listeners: {
+ success: {
+ fn: function(response) {
+ response.results.forEach(result => {
+ if (this.settingsMap.keys.includes(result.key)) {
+ currentSettings.user[result.key] = this.prepareSettingValue(this.settingsMap.xtypes[result.key], result.value);
+ }
+ });
+ buildMap(target, currentSettings);
+ this.fireEvent('actionsReady');
+ },
+ scope: this
+ },
+ failure: {
+ fn: function(response) {
+ this.fireEvent('actionsFail', response);
+ },
+ scope: this
+ }
+ }
+ });
+ }
+ ;
+ this.saveStatus = new MODx.window.SaveProgress({ exitDelay })
+ // start status window
+ this.saveStatus.init();
+
+ this.settingsMap = {
+ keys: [],
+ xtypes: {}
+ };
+ this.valuesMap = {};
+ this.actionMap = {
+ totalActions: 0,
+ actionErrors: [],
+ user: {
+ create: [],
+ update: [],
+ delete: []
+ },
+ global: {
+ create: [],
+ update: []
+ }
+ };
+ Object.entries(values).forEach(([key, value]) => {
+ const settingKey = this.settingsKeys[windowType][key];
+ this.settingsMap.keys.push(settingKey);
+ this.settingsMap.xtypes[settingKey] = this.settingsXtypes[key];
+ if (settingKey.includes('_opacity')) {
+ value = value <= 1 ? parseInt(value * 100) : value ;
+ }
+ this.valuesMap[settingKey] = value;
+ });
+ SetActionMap(settingsTarget);
+ },
+ commitSettingsChanges: function(target) {
+ console.log('commitSettingsChanges :: actionMap', this.actionMap);
+ const
+ userActionBase = 'Security/User/Setting/',
+ globalActionBase = 'System/Settings/',
+ processorsMap = {
+ create: 'Create',
+ update: 'Update',
+ delete: 'Remove'
+ },
+ onSuccess = response => {
+ taskSuccesses++;
+ taskIndex++;
+ console.log(`
+ - - onSuccess - -
+ Incrementing success count to: ${taskSuccesses}
+ Completed ${taskIndex} of ${this.actionMap.totalActions} actions
+
+ `, response);
+ if (taskIndex === this.actionMap.totalActions) {
+ this.fireEvent('actionsDone');
+ }
+ },
+ onFailure = response => {
+ taskFailures++;
+ taskIndex++;
+ console.log(`
+ - - onFailure - -
+ Dang it, something went wrong!!!
+ `, response);
+ // this.fireEvent('actionsFail', response);
+ },
+ baseRequest = {
+ url: MODx.config.connector_url,
+ listeners: {
+ success: { fn: onSuccess },
+ failure: { fn: onFailure }
+ }
+ }
+ ;
+ let
+ taskIndex = 0,
+ taskSuccesses = 0,
+ taskFailures = 0
+ ;
+
+ for (const action in this.actionMap.user) {
+ // console.log('User action processing: ', action);
+ const
+ tasks = this.actionMap.user[action],
+ actionParam = userActionBase + processorsMap[action]
+ ;
+ if (tasks.length > 0) {
+ tasks.forEach(params => {
+ const request = {
+ ...baseRequest,
+ params: { ...params, action: actionParam }
+ };
+ // console.log('Full request obj:', request);
+ MODx.Ajax.request(request);
+ });
+ }
+ }
+ for (const action in this.actionMap.global) {
+ // console.log('Global action processing: ', action);
+ const
+ tasks = this.actionMap.global[action],
+ actionParam = globalActionBase + processorsMap[action]
+ ;
+ if (tasks.length > 0) {
+ tasks.forEach(params => {
+ MODx.Ajax.request({
+ ...baseRequest,
+ params: { ...params, action: actionParam }
+ });
+ });
+ }
+ }
+
+ }
+});
MODx.form.Handler = function(config) {
config = config || {};
diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js
index 00371760026..44df3ff477f 100644
--- a/manager/assets/modext/util/utilities.js
+++ b/manager/assets/modext/util/utilities.js
@@ -200,6 +200,57 @@ MODx.util.safeHtml = function (input, allowedTags, allowedAttributes) {
return input.replace(eventAttributes, 'on$1');
};
+/**
+ * @property {Function} insertTagCopyUtility - Updates placeholder tag in element name's help
+ * field to the current element name and attaches a listener to copy the tag when clicked on
+ *
+ * @param {Object} cmp - The help field's Ext.Component object
+ * @param {String} elType - The MODX element type (i.e., tv, chunk, or snippet)
+ */
+MODx.util.insertTagCopyUtility = function(cmp, elType) {
+ const helpTag = cmp.getEl().child('.example-replace-name'),
+ elTag = cmp.getEl().child('.copy-this');
+ let nameVal = cmp.previousSibling().getValue(),
+ tagText;
+ console.log('helpTag: ',helpTag);
+ // If the helptag isn't available, skip here. This may happen when a lexicon is missing or outdated
+ // and doesn't contain the `example-replace-name` class.
+ if (!helpTag) {
+ return;
+ }
+
+ if (nameVal.length > 0) {
+ helpTag.update(nameVal);
+ tagText = elTag.dom.innerText;
+ }
+
+ helpTag.on({
+ click: function() {
+ nameVal = cmp.previousSibling().getValue();
+ if (nameVal.length > 0) {
+ tagText = elTag.dom.innerText;
+ const tmp = document.createElement('textarea');
+ tmp.value = tagText;
+ document.body.appendChild(tmp);
+ tmp.select();
+ if (document.execCommand('copy')) {
+ const feedback = document.createElement('span');
+ feedback.className = 'element-panel feedback item-copied';
+ feedback.textContent = _(elType+'_tag_copied');
+ elTag.insertSibling(feedback, 'after');
+ setTimeout(function(){
+ feedback.style.opacity = 0;
+ setTimeout(function(){
+ feedback.remove();
+ }, 1200);
+ }, 10);
+ }
+ tmp.remove();
+ }
+ }
+ });
+}
+
/****************************************************************************
* Ext-specific overrides/extensions *
****************************************************************************/
@@ -551,6 +602,30 @@ MODx.util.Format = {
.replace(new RegExp(`[${separator}]{2,}`, 'g'), separator)
;
return padListItems ? formattedList.replaceAll(separator, `${separator} `) : formattedList ;
+ },
+
+ staticElementPathFragment: function(fragment, isDirectoryFragment = false) {
+ fragment = fragment
+ .replace(/[^\w\s-]/gi, '')
+ // .replace(/[\s]+/g, '-')
+ // .toLowerCase()
+ ;
+ fragment = isDirectoryFragment ? fragment.replace(/\s/g, '-') : fragment.replace(/[\s]+/g, '-') ;
+ // Convert nested element categories to nested directory structure
+ if (isDirectoryFragment) {
+ fragment = fragment.replace(/--/gi, '/');
+ fragment = `/${fragment}/`;
+ }
+ return fragment.toLowerCase();
+ },
+
+ fileFullPath: function(path, lowerCaseAll = false) {
+ path = path
+ .replace(/[^\w\s-/]/gi, '')
+ .replace(/[/]{2,}/g, '/')
+ .replace(/[\s]+/g, '-')
+ ;
+ return lowerCaseAll ? path.toLowerCase() : path ;
}
};
@@ -727,6 +802,64 @@ MODx.util.url = {
}
};
+MODx.util.Color = {
+ rgbToHex: rgbString => {
+ if (rgbString.indexOf('#') === 0) {
+ return rgbString;
+ }
+ const
+ sep = rgbString.indexOf(',') > -1 ? ',' : ' ',
+ rgbValues = rgbString.substr(4).split(')')[0].split(sep)
+ ;
+ let r = (+rgbValues[0]).toString(16),
+ g = (+rgbValues[1]).toString(16),
+ b = (+rgbValues[2]).toString(16);
+ if (r.length === 1) { r = `0${r}`; }
+ if (g.length === 1) { g = `0${g}`; }
+ if (b.length === 1) { b = `0${b}`; }
+
+ return `#${r}${g}${b}`;
+ }
+};
+
+MODx.util.Types = {
+ castToBoolean: value => !(
+ (typeof value === 'string' && (['0', 'false', 'no'].includes(value.toLowerCase())))
+ || value === false
+ || value === 0
+ || (Ext.isObject(value) && MODx.util.isEmptyObject(value))
+ || Ext.isEmpty(value)
+ )
+};
+
+MODx.util.isEmptyObject = obj => {
+ if (!Ext.isObject(obj)) {
+ console.warn('The item passed to isEmptyObject is not an object.');
+ return null;
+ }
+ return JSON.stringify(obj) === '{}';
+};
+
+MODx.util.JsonTools = {
+ mapReplacer: (key, value) => {
+ if (value instanceof Map) {
+ return {
+ dataType: 'Map',
+ value: [...value]
+ };
+ }
+ return value;
+ },
+ mapReviver: (key, value) => {
+ if (typeof value === 'object' && value !== null) {
+ if (value.dataType === 'Map') {
+ return new Map(value.value);
+ }
+ }
+ return value;
+ }
+};
+
/**
* Utility methods for tree objects
*/
diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js
index fb88431782b..02fd770b2a2 100644
--- a/manager/assets/modext/widgets/core/modx.grid.js
+++ b/manager/assets/modext/widgets/core/modx.grid.js
@@ -269,6 +269,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
* @param {Object} response - The processor save response object. See modConnectorResponse::outputContent (PHP)
*/
,onAfterAutoSave: function(response) {
+ console.log('onAfterAutoSave running...');
if (!response.success && response.message === '') {
var msg = '';
if (response.data.length) {
@@ -359,6 +360,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
}
,removeActiveRow: function(r) {
+ console.log('removeActiveRow...');
if (this.fireEvent('afterRemoveRow',r)) {
var rx = this.getSelectionModel().getSelected();
this.getStore().remove(rx);
diff --git a/manager/assets/modext/widgets/core/modx.grid.settings.js b/manager/assets/modext/widgets/core/modx.grid.settings.js
index ba4ebeef9ff..c8609f52256 100644
--- a/manager/assets/modext/widgets/core/modx.grid.settings.js
+++ b/manager/assets/modext/widgets/core/modx.grid.settings.js
@@ -281,7 +281,8 @@ MODx.grid.SettingsGrid = function(config = {}) {
,scrollOffset: 0
});
MODx.grid.SettingsGrid.superclass.constructor.call(this,config);
- this.addEvents('createSetting', 'updateSetting');
+ this.addEvents('createSetting', 'updateSetting', 'updateSettingFromGrid', 'removeSettingFromGrid');
+ console.log(`Default Src: ${MODx.config.default_media_source}`);
const gridFilterData = [
{ filterId: 'filter-ns', dependentParams: ['area'] },
@@ -290,17 +291,57 @@ MODx.grid.SettingsGrid = function(config = {}) {
this.on({
createSetting: function(...args) {
+ // console.log('createSetting args', ...args);
if (args[0].a.response.status === 200) {
this.refreshFilterOptions(gridFilterData);
+ if (args[0].a.result.object.key.indexOf('mask_') === 0) {
+ MODx.maskConfig.fireEvent('syncSettingFromGrid', {
+ action: 'create',
+ data: args[0].a.result.object,
+ gridType: this.settingsType
+ });
+ // MODx.maskConfig.fireEvent('createSettingFromGrid', args[0].a.result.object);
+ }
}
},
updateSetting: function(...args) {
+ // console.log('updateSetting args', ...args);
if (args[0].a.response.status === 200) {
this.refreshFilterOptions(gridFilterData);
+ if (args[0].a.result.object.key.indexOf('mask_') === 0) {
+ MODx.maskConfig.fireEvent('syncSettingFromGrid', {
+ action: 'update',
+ data: args[0].a.result.object,
+ gridType: this.settingsType
+ });
+ // MODx.maskConfig.fireEvent('updateSettingFromGrid', args[0].a.result.object);
+ }
}
},
- afterRemoveRow: function() {
+ afterAutoSave: function(response) {
+ if (response.success && response.object.key.indexOf('mask_') === 0) {
+ MODx.maskConfig.fireEvent('syncSettingFromGrid', {
+ action: 'update',
+ data: response.object,
+ gridType: this.settingsType
+ });
+ // MODx.maskConfig.fireEvent('updateSettingFromGrid', response.object);
+ }
+ },
+ afterRemoveRow: function(record) {
this.refreshFilterOptions(gridFilterData);
+ if (record && record.key.indexOf('mask_') === 0) {
+ // const target = this.settingsType || this.getSettingsType();
+ MODx.maskConfig.fireEvent('syncSettingFromGrid', {
+ action: 'remove',
+ data: record,
+ gridType: this.settingsType
+ });
+ // MODx.maskConfig.fireEvent('removeSettingFromGrid', {
+ // record,
+ // target
+ // });
+ }
}
});
diff --git a/manager/assets/modext/widgets/core/modx.panel.js b/manager/assets/modext/widgets/core/modx.panel.js
index 3d6681c7d61..23b39f45dd6 100644
--- a/manager/assets/modext/widgets/core/modx.panel.js
+++ b/manager/assets/modext/widgets/core/modx.panel.js
@@ -87,53 +87,69 @@ Ext.extend(MODx.FormPanel,Ext.FormPanel,{
,errorHandlingTabs: []
,errorHandlingIgnoreTabs: []
- ,submit: function(o) {
- var fm = this.getForm();
- if (fm.isValid() || o.bypassValidCheck) {
- o = o || {};
- o.headers = {
- 'Powered-By': 'MODx'
- ,'modAuth': MODx.siteId
+ ,submit: function(options = {}) {
+ const form = this.getForm();
+ if (form.isValid() || options.bypassValidCheck) {
+ const
+ exitDelay = 150,
+ status = new MODx.window.SaveProgress({ exitDelay })
+ ;
+ status.init();
+ options.headers = {
+ 'Powered-By': 'MODx',
+ modAuth: MODx.siteId
};
- if (this.fireEvent('beforeSubmit',{
- form: fm
- ,options: o
- ,config: this.config
+ if (this.fireEvent('beforeSubmit', {
+ form: form,
+ options: options,
+ config: this.config
})) {
- fm.submit({
- waitMsg: this.config.saveMsg || _('saving')
- ,scope: this
- ,headers: o.headers
- ,clientValidation: (o.bypassValidCheck ? false : true)
- ,failure: function(f,a) {
- if (this.fireEvent('failure',{
- form: f
- ,result: a.result
- ,options: o
- ,config: this.config
- })) {
- MODx.form.Handler.errorExt(a.result,f);
- }
- }
- ,success: function(f,a) {
+ form.submit({
+ scope: this,
+ headers: options.headers,
+ clientValidation: !options.bypassValidCheck,
+ failure: function(f, a) {
+ /*
+ Need to allow time for the status window to finish
+ closing, otherwise it becomes unreachable when the
+ error message alert is shown (and even after it is dismissed)
+ */
+ setTimeout(() => {
+ if (this.fireEvent('failure', {
+ form: f,
+ result: a.result,
+ options: options,
+ config: this.config
+ })) {
+ status.exit('failure');
+ setTimeout(() => {
+ MODx.form.Handler.errorExt(a.result, f);
+ }, exitDelay);
+ }
+ }, exitDelay);
+ },
+ success: function(f, a) {
if (this.config.success) {
- Ext.callback(this.config.success,this.config.scope || this,[f,a]);
+ Ext.callback(this.config.success, this.config.scope || this, [f, a]);
}
- this.fireEvent('success',{
- form: f
- ,result: a.result
- ,options: o
- ,config: this.config
+ this.fireEvent('success', {
+ form: f,
+ result: a.result,
+ options: options,
+ config: this.config
});
+ status.exit();
this.clearDirty();
- this.fireEvent('setup',this.config);
+ this.fireEvent('setup', this.config);
- //get our Active input value and keep focus
- var lastActiveEle = Ext.state.Manager.get('curFocus');
- if (lastActiveEle && lastActiveEle != '') {
+ // get our Active input value and keep focus
+ const lastActiveEle = Ext.state.Manager.get('curFocus');
+ if (lastActiveEle && lastActiveEle !== '') {
Ext.state.Manager.clear('curFocus');
- var initFocus = document.getElementById(lastActiveEle);
- if(initFocus) initFocus.focus();
+ const initFocus = document.getElementById(lastActiveEle);
+ if (initFocus) {
+ initFocus.focus();
+ }
}
}
});
@@ -478,56 +494,11 @@ Ext.extend(MODx.FormPanel,Ext.FormPanel,{
}
/**
- * @property {Function} insertTagCopyUtility - Updates placeholder tag in element name's help
- * field to the current element name and attaches a listener to copy the tag when clicked on
- *
- * @param {Object} cmp - The help field's Ext.Component object
- * @param {String} elType - The MODX element type (i.e., tv, chunk, or snippet)
+ * Moved this functionality to utilities.js. Passing through for BC, but
+ * deprecate usage here and will remove in future release.
*/
,insertTagCopyUtility: function(cmp, elType) {
- const helpTag = cmp.getEl().child('.example-replace-name'),
- elTag = cmp.getEl().child('.copy-this')
- ;
- let nameVal = cmp.previousSibling().getValue(),
- tagText
- ;
-
- // If the helptag isn't available, skip here. This may happen when a lexicon is missing or outdated
- // and doesn't contain the `example-replace-name` class.
- if (!helpTag) {
- return;
- }
-
- if (nameVal.length > 0) {
- helpTag.update(nameVal);
- tagText = elTag.dom.innerText;
- }
-
- helpTag.on({
- click: function() {
- nameVal = cmp.previousSibling().getValue();
- if (nameVal.length > 0) {
- tagText = elTag.dom.innerText;
- const tmp = document.createElement('textarea');
- tmp.value = tagText;
- document.body.appendChild(tmp);
- tmp.select();
- if (document.execCommand('copy')) {
- const feedback = document.createElement('span');
- feedback.className = 'element-panel feedback item-copied';
- feedback.textContent = _(elType+'_tag_copied');
- elTag.insertSibling(feedback, 'after');
- setTimeout(function(){
- feedback.style.opacity = 0;
- setTimeout(function(){
- feedback.remove();
- }, 1200);
- }, 10);
- }
- tmp.remove();
- }
- }
- });
+ MODx.util.insertTagCopyUtility(cmp, elType);
}
/**
diff --git a/manager/assets/modext/widgets/core/modx.window.js b/manager/assets/modext/widgets/core/modx.window.js
index db98205d901..8f7294d5caa 100644
--- a/manager/assets/modext/widgets/core/modx.window.js
+++ b/manager/assets/modext/widgets/core/modx.window.js
@@ -2,102 +2,116 @@
// these also apply for Windows that do not extend MODx.Window (like console for ex.)
// we use CSS3 box-shadows in 2014, removes clutter from the DOM
Ext.Window.prototype.floating = { shadow: false };
-/* override default Ext.Window component methods */
+
Ext.override(Ext.Window, {
- // prevents ugly slow js animations when opening a window
- // we cannot do the CSS3 animations stuff in these overrides, as not all windows are animated!
- // so they just prevent the normal JS animation to take effect
animShow: function() {
this.afterShow();
-
- // some windows (like migx) don't seem to call onShow
- // so we have to do a check here after onShow should have finished
- var win = this; // we need a reference to this for setTimeout
- // wait for onShow to finish and check if the window is already visible then, if not, try to do that
- setTimeout(function() {
- if (!win.el.hasClass('anim-ready')) {
- win.el.addClass('anim-ready');
- setTimeout(function() {
- if (win.mask !== undefined) {
- // respect that the mask is not always the same object
- if (win.mask instanceof Ext.Element) {
- win.mask.addClass('fade-in');
- } else {
- win.mask.el.addClass('fade-in');
- }
+ },
+ animHide: function() {
+ this.afterHide();
+ },
+ render: function(...args) {
+ // ...args contains only one arg at index 0 - a ref to the body el
+ // console.log('Ext.Window (base class) render... is modal?', this.modal);
+ this.on({
+ beforeshow: function() {
+ const
+ window = this,
+ windowType = this.getWindowType(window),
+ maskObject = windowType === 'modal' ? this.mask : MODx.mask,
+ showMask = !MODx.maskConfig.getMaskAttribute(windowType, 'disabled')
+ ;
+ // console.log(`SHOW : ${showMask}`);
+ if (showMask && windowType === 'modal') {
+ this.mask.addClass('modal');
+ maskObject.dom.style.backgroundColor = MODx.maskConfig.getMaskAttribute(windowType, 'color');
+ }
+ this.el.addClass('anim-ready');
+ setTimeout(() => {
+ if (showMask) {
+ this.toggleMask(windowType, maskObject);
}
- win.el.addClass('zoom-in');
+ window.el.addClass('zoom-in');
}, 250);
- }
- }, 300);
- }
- ,animHide: function() {
- this.afterHide();
+ },
+ beforehide: function() {
+ if (this.el.hasClass('zoom-in')) {
+ const
+ window = this,
+ windowType = this.getWindowType(window),
+ maskObject = windowType === 'modal' ? this.mask : MODx.mask,
+ hideMask = window.id !== 'modx-window-configure-mask'
+ && (windowType === 'modal'
+ || (windowType === 'pseudomodal'
+ && MODx.openPseudoModals.length <= 1)
+ )
+ ;
+ // console.log(`beforehide :: Hiding a ${windowType} window...\nArgs:`, arguments, '\nOpen modals:', MODx.openPseudoModals);
- }
- ,onShow: function() {
- // skip MODx.msg windows, the animations do not work with them as they are always the same element!
- if (!this.el.hasClass('x-window-dlg')) {
- // first set the class that scales the window down a bit
- // this has to be done after the full window is positioned correctly by extjs
- this.addClass('anim-ready');
- // let the scale transformation to 0.7 finish before animating in
- var win = this; // we need a reference to this for setTimeout
- setTimeout(function() {
- if (win.mask !== undefined) {
- // respect that the mask is not always the same object
- if (win.mask instanceof Ext.Element) {
- win.mask.addClass('fade-in');
- } else {
- win.mask.el.addClass('fade-in');
+ this.el.removeClass('zoom-in');
+ this.el.addClass('zoom-out');
+ if (hideMask) {
+ this.toggleMask(windowType, maskObject, 'hide');
}
+ this.hidden = true;
+ setTimeout(() => {
+ if (!this.isDestroyed) {
+ window.el.removeClass('zoom-out');
+ window.el.removeClass('anim-ready');
+ window.el.hide();
+ window.afterHide();
+ if (hideMask) {
+ Ext.getBody().removeClass('x-body-masked');
+ }
+ }
+ }, 250);
}
- win.el.addClass('zoom-in');
- }, 250);
- } else {
- // we need to handle MODx.msg windows (Ext.Msg singletons, e.g. always the same element, no multiple instances) differently
- this.mask.addClass('fade-in');
- this.el.applyStyles({'opacity': 1});
- }
- }
- ,onHide: function() {
- // for some unknown (to me) reason, onHide() get's called when a window is initialized, e.g. before onShow()
- // so we need to prevent the following routine be applied prematurely
- if (this.el.hasClass('zoom-in')) {
- this.el.removeClass('zoom-in');
- if (this.mask !== undefined) {
- // respect that the mask is not always the same object
- if (this.mask instanceof Ext.Element) {
- this.mask.removeClass('fade-in');
- } else {
- this.mask.el.removeClass('fade-in');
- }
+ return false;
}
- this.addClass('zoom-out');
- // let the CSS animation finish before hiding the window
- var win = this; // we need a reference to this for setTimeout
- setTimeout(function() {
- // we have an unsolved problem with windows that are destroyed on hide
- // the zoom-out animation cannot be applied for such windows, as they
- // get destroyed too early, if someone knows a solution, please tell =)
- if (!win.isDestroyed) {
- win.el.hide();
- // and remove the CSS3 animation classes
- win.el.removeClass('zoom-out');
- win.el.removeClass('anim-ready');
- }
- }, 250);
- } else if (this.el.hasClass('x-window-dlg')) {
- // we need to handle MODx.msg windows (Ext.Msg singletons, e.g. always the same element, no multiple instances) differently
- this.el.applyStyles({'opacity': 0});
-
- if (this.mask !== undefined) {
- // respect that the mask is not always the same object
- if (this.mask instanceof Ext.Element) {
- this.mask.removeClass('fade-in');
- } else {
- this.mask.el.removeClass('fade-in');
- }
+ });
+ Ext.Window.superclass.render.call(this, ...args);
+ },
+
+ /**
+ *
+ * @param {*} window
+ * @returns
+ */
+ getWindowType: function(window) {
+ return window.el.hasClass('x-window-dlg') || window.modal === true ? 'modal' : 'pseudomodal';
+ },
+
+ /**
+ * Controls the visibility of the masking element by applying or removing a specified css selector
+ * @param {String} windowType Type of window being worked with (i.e., 'modal' or 'pseudomodal')
+ * @param {Ext.Element|Object} maskElement
+ * @param {String} action The toggle state being applied: 'show' or 'hide'
+ */
+ toggleMask: function(windowType, maskElement, action = 'show') {
+ if (maskElement === undefined) {
+ return;
+ }
+ const
+ targetElement = maskElement instanceof Ext.Element ? maskElement : maskElement?.el
+ ;
+ if (targetElement) {
+ if (action === 'hide') {
+ // console.log('1 :: toggle targetEl:', targetElement);
+ targetElement.dom.style.removeProperty('opacity');
+ setTimeout(() => {
+ // console.log('2 :: toggle targetEl:', targetElement);
+ /*
+ Sometimes an empty Ext.Element (with only an id) will be present
+ by the time this runs, so ensure we only try to hide Elements that
+ can be hidden to avoid errors
+ */
+ if (Object.hasOwn(targetElement, 'dom')) {
+ targetElement.hide();
+ }
+ }, 1000);
+ } else if (this.id !== 'modx-window-status-modal') {
+ // console.log('Showing status win mask');
+ targetElement.dom.style.opacity = MODx.maskConfig.getMaskAttribute(windowType, 'opacity');
}
}
}
@@ -112,76 +126,418 @@ Ext.override(Ext.Window, {
* @param {Object} config An object of options.
* @xtype modx-window
*/
-MODx.Window = function(config) {
- config = config || {};
- Ext.applyIf(config,{
- modal: false
- ,layout: 'auto'
- ,closeAction: 'hide'
- ,shadow: true
- ,resizable: true
- ,collapsible: true
- ,maximizable: true
- ,autoHeight: false
- ,autoScroll: true
- ,allowDrop: true
- ,width: 400
- ,constrain: true
- ,constrainHeader: true
- ,cls: 'modx-window'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { config.closeAction !== 'close' ? this.hide() : this.close(); }
- },{
- text: config.saveBtnText || _('save')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
- ,record: {}
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,fn: function(keyCode, event) {
- var elem = event.getTarget();
- var component = Ext.getCmp(elem.id);
+MODx.Window = function(config = {}) {
+ this.isSmallScreen = Ext.getBody().getViewSize().height <= 768;
+ /*
+ Update boolean modxFbarHas[___]SaveSwitch properties for later use
+ */
+ if (Object.hasOwn(config, 'modxFbarSaveSwitches') && config.modxFbarSaveSwitches.length > 0) {
+ config.modxFbarSaveSwitches.forEach(saveSwitch => {
+ saveSwitch = saveSwitch[0].toUpperCase() + saveSwitch.slice(1);
+ const configKey = `modxFbarHas${saveSwitch}Switch`;
+ config[configKey] = true;
+ });
+ }
+ /*
+ Setup the standard system footer bar if fbar and buttons properties are empty.
+ Note that buttons overrides fbar and can be used to specify a customized
+ set of window buttons.
+ */
+ if (!Object.hasOwn(config, 'fbar') && (!Object.hasOwn(config, 'buttons') || config.buttons.length === 0)) {
+ const footerBar = this.getWindowFbar(config);
+ if (footerBar) {
+ config.buttonAlign = 'left';
+ config.fbar = footerBar;
+ }
+ }
+ Ext.applyIf(config, {
+ modal: false,
+
+ modxFbarHasClearCacheSwitch: false,
+ modxFbarHasDuplicateValuesSwitch: false,
+ modxFbarHasRedirectSwitch: false,
+
+ modxFbarButtons: config.modxFbarButtons || 'c-s',
+ modxFbarSaveSwitches: [],
+ /*
+ Windows are pseudomodal by default unless:
+ 1] a config value is passed
+ 2] the window's modal property is set to true
+ */
+ modxPseudoModal: Ext.isBoolean(config.modxPseudoModal) ? config.modxPseudoModal : !config.modal,
+
+ layout: 'auto',
+ closeAction: 'hide',
+ shadow: true,
+ resizable: true,
+ collapsible: true,
+ maximizable: true,
+ autoHeight: false,
+ autoScroll: true,
+ allowDrop: true,
+ width: 400,
+ constrain: true,
+ constrainHeader: true,
+ cls: 'modx-window',
+ record: {},
+ keys: [
+ {
+ key: Ext.EventObject.ENTER,
+ fn: function(keyCode, event) {
+ const
+ elem = event.getTarget(),
+ component = Ext.getCmp(elem.id)
+ ;
if (component instanceof Ext.form.TextArea) {
- return component.append("\n");
- } else {
- this.submit();
+ return component.append('\n');
}
-
+ this.submit();
+ },
+ scope: this
+ }, {
+ key: Ext.EventObject.RIGHT,
+ alt: true,
+ handler: function(keyCode, event) {
+ console.log('Alt right');
+ if (MODx.openPseudoModals.length > 1) {
+ console.log('Key shortcut :: focus next window...');
+ }
+ },
+ scope: this
+ }, {
+ key: Ext.EventObject.LEFT,
+ alt: true,
+ handler: function(keyCode, event) {
+ console.log('Alt left');
+ if (MODx.openPseudoModals.length > 1) {
+ console.log('Key shortcut :: focus prev window...');
+ }
+ },
+ scope: this
+ }
+ ],
+ tools: [{
+ id: 'gear',
+ title: _('mask_toolbar_tool_title'),
+ qtip: _('mask_toolbar_tool_qtip'),
+ handler: function(evt, toolEl, panel, toolConfig) {
+ const targetWindowType = panel.getWindowType(panel);
+ let
+ configWindow = Ext.getCmp('modx-window-configure-mask'),
+ mask = document.querySelector(`.ext-el-mask.${targetWindowType}`)
+ ;
+ const
+ isDisabled = MODx.maskConfig.getMaskAttribute(targetWindowType, 'disabled'),
+ maskStyles = mask && !isDisabled ? window.getComputedStyle(mask) : null,
+ opacity = maskStyles
+ ? maskStyles.opacity
+ : MODx.maskConfig.getMaskAttribute(targetWindowType, 'opacity'),
+ bgColor = maskStyles
+ ? MODx.util.Color.rgbToHex(maskStyles.backgroundColor)
+ : MODx.maskConfig.getMaskAttribute(targetWindowType, 'color'),
+ onColorInput = e => {
+ mask.style.backgroundColor = e.target.value;
+ },
+ onOpacityInput = e => {
+ mask.style.opacity = e.target.value / 100;
+ },
+ setFieldsDisabled = (fieldMap, disabled = true, selectAll = false) => {
+ const filterList = [
+ 'modx-mask-settings--opacity',
+ 'modx-mask-settings--color'
+ ];
+ Object.keys(fieldMap).forEach(fieldKey => {
+ if (selectAll === true || filterList.includes(fieldKey)) {
+ fieldMap[fieldKey].setDisabled(disabled);
+ }
+ });
+ },
+ /**
+ * Controlled destruction of window needed to allow animation to work properly
+ */
+ dismiss = windowCmp => {
+ if (windowCmp instanceof MODx.Window) {
+ const
+ colorInput = document.getElementById('modx-mask-color'),
+ opacityInput = document.getElementById('modx-mask-opacity')
+ ;
+ colorInput.removeEventListener('input', onColorInput);
+ opacityInput.removeEventListener('input', onOpacityInput);
+ windowCmp.hide();
+ setTimeout(() => windowCmp.destroy(), 250);
+ }
+ }
+ ;
+ if (!configWindow) {
+ configWindow = new MODx.Window({
+ title: _('mask_config_window_title'),
+ width: panel.width - 100,
+ id: 'modx-window-configure-mask',
+ cls: 'modx-window configure',
+ modxPseudoModal: false,
+ autoHeight: true,
+ fields: [
+ {
+ xtype: 'checkbox',
+ itemId: 'modx-mask-settings--disabled',
+ boxLabel: _('mask_config_field_disabled'),
+ description: MODx.expandHelp ? '' : _('mask_config_field_disabled_desc'),
+ checked: isDisabled,
+ listeners: {
+ check: function(cmp, checked) {
+ const
+ form = cmp.ownerCt.getForm(),
+ fields = form.items.map
+ ;
+ if (checked) {
+ mask.style.opacity = 0;
+ setFieldsDisabled(fields);
+ } else {
+ if (!mask) {
+ const maskCmp = MODx.maskConfig.createMask(panel, targetWindowType, 'configure');
+ mask = maskCmp.dom;
+ maskCmp.show();
+ }
+ mask.style.opacity = opacity;
+ setFieldsDisabled(fields, false, true);
+ }
+ }
+ }
+ },
+ {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('mask_config_field_disabled_desc'),
+ cls: 'desc-under toggle-slider-above'
+ },
+ {
+ xtype: 'textfield',
+ itemId: 'modx-mask-settings--opacity',
+ id: 'modx-mask-opacity',
+ inputType: 'range',
+ fieldLabel: _('mask_config_field_opacity'),
+ min: 5,
+ max: 95,
+ step: 5,
+ disabled: isDisabled,
+ value: opacity <= 1 ? opacity * 100 : opacity
+ },
+ {
+ xtype: 'textfield',
+ itemId: 'modx-mask-settings--color',
+ id: 'modx-mask-color',
+ inputType: 'color',
+ fieldLabel: _('mask_config_field_color'),
+ enableKeyEvents: true,
+ disabled: isDisabled,
+ value: bgColor
+ },
+ {
+ xtype: 'checkbox',
+ itemId: 'modx-mask-settings--set-user',
+ boxLabel: 'Update User Settings',
+ description: MODx.expandHelp ? '' : _('mask_config_field_update_user_desc'),
+ disabled: isDisabled
+ },
+ {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('mask_config_field_update_user_desc'),
+ cls: 'desc-under toggle-slider-above'
+ },
+ {
+ xtype: 'checkbox',
+ itemId: 'modx-mask-settings--set-global',
+ boxLabel: _('mask_config_field_update_global'),
+ description: MODx.expandHelp ? '' : _('mask_config_field_update_global_desc'),
+ disabled: isDisabled
+ },
+ {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('mask_config_field_update_global_desc'),
+ cls: 'desc-under toggle-slider-above'
+ }
+ ],
+ buttons: [
+ {
+ text: _('cancel'),
+ handler: function(btn, e) {
+ mask.style.backgroundColor = MODx.maskConfig.getMaskAttribute(targetWindowType, 'color');
+ mask.style.opacity = isDisabled ? 0 : MODx.maskConfig.getMaskAttribute(targetWindowType, 'opacity');
+ dismiss(configWindow);
+ }
+ },
+ {
+ text: _('save'),
+ cls: 'primary-button',
+ handler: function(btn, e) {
+ const
+ form = configWindow.fp.getForm(),
+ fields = form.items,
+ values = {
+ disabled: Boolean(fields.map['modx-mask-settings--disabled'].getValue()),
+ color: MODx.util.Color.rgbToHex(fields.map['modx-mask-settings--color'].getValue()),
+ opacity: fields.map['modx-mask-settings--opacity'].getValue() / 100
+ },
+ saveToGlobalSettings = Boolean(fields.map['modx-mask-settings--set-global'].getValue()),
+ saveToUserSettings = Boolean(fields.map['modx-mask-settings--set-user'].getValue())
+ ;
+ if (!saveToGlobalSettings && !saveToUserSettings) {
+ /*
+ - Show confirm window stating changes only last for session,
+ with a 'Do not show this warning again' checkbox (persisted
+ in a localStorage item).
+ - Will need to check two condiditions (depends on if user is sudo
+ or primary (id = 1) user, where both save switches will be available)
+ */
+ console.log('Let’s show a dialog confirming changes will be lost at end of session...');
+ }
+ MODx.maskConfig.updateSessionConfig(targetWindowType, values);
+ if (saveToGlobalSettings || saveToUserSettings) {
+ let settingsTarget;
+ if (saveToGlobalSettings && saveToUserSettings) {
+ settingsTarget = 'both';
+ } else {
+ settingsTarget = saveToGlobalSettings ? 'global' : 'user';
+ }
+ MODx.maskConfig.updateSystemSettings(targetWindowType, settingsTarget, values, MODx.config.user);
+ }
+ dismiss(configWindow);
+ }
+ }
+ ],
+ tools: [{
+ id: 'close',
+ handler: function() {
+ dismiss(configWindow);
+ }
+ }],
+ listeners: {
+ afterrender: function(cmp) {
+ const { tools } = cmp;
+ if (tools) {
+ Object.keys(tools).forEach(tool => {
+ if (tool !== 'close') {
+ tools[tool].hide();
+ }
+ });
+ }
+ }
+ },
+ onEsc: function() {
+ dismiss(configWindow);
+ }
+ });
+ configWindow.show(evt.target);
+ // console.log('config win close action: ', configWindow.closeAction);
}
- ,scope: this
+ configWindow.toFront();
+ /*
+ Show live adjustments to mask settings
+
+ Note: Setting up listeners here and not on the opacity and color Ext components
+ above because we need to listen for the 'input' event (which is not defined in Ext 3.4)
+ for range and color types. While we could extend the textfield (or its base) component
+ to define/add that listener for global use, electing to keep it simple here.
+ */
+ const
+ colorInput = document.getElementById('modx-mask-color'),
+ opacityInput = document.getElementById('modx-mask-opacity')
+ ;
+ colorInput.addEventListener('input', onColorInput);
+ opacityInput.addEventListener('input', onOpacityInput);
+ }
}]
});
- MODx.Window.superclass.constructor.call(this,config);
+ MODx.Window.superclass.constructor.call(this, config);
this.options = config;
this.config = config;
-
this.addEvents({
- success: true
- ,failure: true
- ,beforeSubmit: true
- ,updateWindow: false
+ success: true,
+ failure: true,
+ beforeSubmit: true
});
this._loadForm();
- this.on('show',function() {
- if (this.config.blankValues) { this.fp.getForm().reset(); }
- if (this.config.allowDrop) { this.loadDropZones(); }
- this.syncSize();
- this.focusFirstField();
- },this);
- this.on('afterrender', function() {
- this.originalHeight = this.el.getHeight();
- this.toolsHeight = this.originalHeight - this.body.getHeight() + 50;
- this.resizeWindow();
+ this.on({
+ render: function() {
+ if (this.modxPseudoModal && !MODx.maskConfig.getMaskAttribute('pseudomodal', 'disabled')) {
+ MODx.maskConfig.createMask(this, 'pseudomodal', 'render', false);
+ }
+ },
+ afterrender: function() {
+ this.originalHeight = this.el.getHeight();
+ this.toolsHeight = this.originalHeight - this.body.getHeight() + 50;
+ this.resizeWindow();
+ },
+ beforeShow: function() {
+ if (this.modxPseudoModal && !MODx.util.isEmptyObject(MODx.mask) && !MODx.mask?.isVisible()) {
+ Ext.getBody().addClass('x-body-masked');
+ MODx.mask.show();
+ }
+ },
+ show: function() {
+ if (this.modxPseudoModal) {
+ this.registerPseudomodal(this);
+ }
+ if (this.config.blankValues) {
+ this.fp.getForm().reset();
+ }
+ if (this.config.allowDrop) {
+ this.loadDropZones();
+ }
+ this.syncSize();
+ this.focusFirstField();
+ },
+ hide: function() {
+ if (this.modxPseudoModal) {
+ this.unregisterPseudomodal(this.getWindowIdentifier());
+ }
+ /*
+ Re-focus one of the open windows, if any, so the esc key
+ can be used to close each successive open window
+
+ TODO: Track all non-dialog modals in obj that will replace
+ MODx.openPseudoModals; it should take the shape of -
+ ###
+ MODx.openModals = {
+ pseudo: [
+ {
+ windowId: stringid,
+ window: windowObj
+ },
+ ...
+ ],
+ // Note: There can only be one standard modal open at a time
+ // A single configuration and/or dialog modal may coexist on top of either the standard or pseudo
+ standard: [
+ {
+ windowId: stringid,
+ window: windowObj
+ }
+ ]
+ }
+ ###
+ */
+ if (MODx.openPseudoModals.length > 0) {
+ console.log('Bringing first pseudomodal to front...', MODx.openPseudoModals);
+ MODx.openPseudoModals[0].window.toFront();
+ }
+ },
+ destroy: function() {
+ if (this.modxPseudoModal) {
+ this.unregisterPseudomodal(this.getWindowIdentifier());
+ }
+ }
});
Ext.EventManager.onWindowResize(this.resizeWindow, this);
};
-Ext.extend(MODx.Window,Ext.Window,{
+Ext.extend(MODx.Window, Ext.Window, {
_loadForm: function() {
- if (this.checkIfLoaded(this.config.record || null)) { return false; }
+ if (this.checkIfLoaded(this.config.record || null)) {
+ console.log('Form already loaded');
+ return false;
+ }
var r = this.config.record;
/* set values here, since setValue after render seems to be broken */
@@ -198,10 +554,34 @@ Ext.extend(MODx.Window,Ext.Window,{
}
}
}
+
+ /*
+ When a switch is rendered in the footer bar, we need to
+ insert a hidden field in the form to to be able to relay its value to
+ the processor
+ */
+ if (Object.hasOwn(this.config, 'modxFbarSaveSwitches') && this.config.modxFbarSaveSwitches.length > 0) {
+ // console.log('We have footer bar switches to build!');
+ this.config.modxFbarSaveSwitches.forEach(saveSwitch => {
+ let defaultValue = 1;
+ // console.log('saveSwitch: ', saveSwitch);
+ switch (saveSwitch) {
+ case 'redirect':
+ defaultValue = this.config.redirect === false ? 0 : 1;
+ break;
+ case 'duplicateValues':
+ defaultValue = 0;
+ break;
+ // no default
+ }
+ this.setFbarSwitchHiddenField(saveSwitch, defaultValue);
+ });
+ }
+ // console.log('final fields: ', this.config.fields);
this.fp = this.createForm({
- url: this.config.url
- ,baseParams: this.config.baseParams || { action: this.config.action || '' }
- ,items: this.config.fields || []
+ url: this.config.url,
+ baseParams: this.config.baseParams || { action: this.config.action || '' },
+ items: this.config.fields || []
});
var w = this;
this.fp.getForm().items.each(function(f) {
@@ -210,87 +590,118 @@ Ext.extend(MODx.Window,Ext.Window,{
});
});
this.renderForm();
- }
+ },
- ,focusFirstField: function() {
+ focusFirstField: function() {
if (this.fp && this.fp.getForm() && this.fp.getForm().items.getCount() > 0) {
var fld = this.findFirstTextField();
- if (fld) { fld.focus(false,200); }
+ if (fld) { fld.focus(false, 200); }
}
- }
- ,findFirstTextField: function(i) {
+ },
+
+ findFirstTextField: function(i) {
i = i || 0;
var fld = this.fp.getForm().items.itemAt(i);
- if (!fld) return false;
+ if (!fld) { return false; }
if (fld.isXType('combo') || fld.isXType('checkbox') || fld.isXType('radio') || fld.isXType('displayfield') || fld.isXType('statictextfield') || fld.isXType('hidden')) {
i = i+1;
fld = this.findFirstTextField(i);
}
return fld;
- }
+ },
- ,submit: function(close) {
- close = close === false ? false : true;
- var f = this.fp.getForm();
- if (f.isValid() && this.fireEvent('beforeSubmit',f.getValues())) {
+ submit: function(closeOnSuccess) {
+ const
+ close = closeOnSuccess !== false,
+ f = this.fp.getForm()
+ ;
+ if (f.isValid() && this.fireEvent('beforeSubmit', f.getValues())) {
+ const
+ exitDelay = 150,
+ status = new MODx.window.SaveProgress({ exitDelay })
+ ;
+ status.init();
f.submit({
- waitMsg: this.config.waitMsg || _('saving')
- ,submitEmptyText: this.config.submitEmptyText !== false
- ,scope: this
- ,failure: function(frm,a) {
- if (this.fireEvent('failure',{f:frm,a:a})) {
- MODx.form.Handler.errorExt(a.result,frm);
- }
- this.doLayout();
- }
- ,success: function(frm,a) {
+ submitEmptyText: this.config.submitEmptyText !== false,
+ scope: this,
+ failure: function(frm, a) {
+ /*
+ Need to allow time for the status window to finish
+ closing, otherwise it becomes unreachable when the
+ error message alert is shown (and even after it is dismissed)
+ */
+ setTimeout(() => {
+ if (this.fireEvent('failure', {
+ f: frm,
+ a: a
+ })) {
+ status.exit('failure');
+ setTimeout(() => {
+ MODx.form.Handler.errorExt(a.result, frm);
+ }, exitDelay);
+ }
+ this.doLayout();
+ }, exitDelay);
+ },
+ success: function(frm, a) {
if (this.config.success) {
- Ext.callback(this.config.success,this.config.scope || this,[frm,a]);
+ Ext.callback(this.config.success, this.config.scope || this, [frm, a]);
}
- this.fireEvent('success',{f:frm,a:a});
- if (close) { this.config.closeAction !== 'close' ? this.hide() : this.close(); }
+ this.fireEvent('success', {
+ f: frm,
+ a: a
+ });
+ if (close) {
+ if (this.config.closeAction !== 'close') {
+ this.hide();
+ } else {
+ this.close();
+ }
+ }
+ status.exit();
this.doLayout();
}
});
}
- }
+ },
- ,createForm: function(config) {
- Ext.applyIf(this.config,{
- formFrame: true
- ,border: false
- ,bodyBorder: false
- ,autoHeight: true
+ createForm: function(config) {
+ Ext.applyIf(this.config, {
+ formFrame: true,
+ border: false,
+ bodyBorder: false,
+ autoHeight: true
});
config = config || {};
- Ext.applyIf(config,{
- labelAlign: this.config.labelAlign || 'top'
- ,labelWidth: this.config.labelWidth || 100
- ,labelSeparator: this.config.labelSeparator || ''
- ,frame: this.config.formFrame
- ,border: this.config.border
- ,bodyBorder: this.config.bodyBorder
- ,autoHeight: this.config.autoHeight
- ,anchor: '100% 100%'
- ,errorReader: MODx.util.JSONReader
- ,defaults: this.config.formDefaults || {
- msgTarget: this.config.msgTarget || 'under'
- }
- ,url: this.config.url
- ,baseParams: this.config.baseParams || {}
- ,fileUpload: this.config.fileUpload || false
+ Ext.applyIf(config, {
+ labelAlign: this.config.labelAlign || 'top',
+ labelWidth: this.config.labelWidth || 100,
+ labelSeparator: this.config.labelSeparator || '',
+ frame: this.config.formFrame,
+ border: this.config.border,
+ bodyBorder: this.config.bodyBorder,
+ autoHeight: this.config.autoHeight,
+ anchor: '100% 100%',
+ errorReader: MODx.util.JSONReader,
+ defaults: this.config.formDefaults || {
+ msgTarget: this.config.msgTarget || 'under',
+ anchor: '100%'
+ },
+ url: this.config.url,
+ baseParams: this.config.baseParams || {},
+ fileUpload: this.config.fileUpload || false
});
return new Ext.FormPanel(config);
- }
+ },
- ,renderForm: function() {
+ renderForm: function() {
this.fp.on('destroy', function() {
Ext.EventManager.removeResizeListener(this.resizeWindow, this);
}, this);
this.add(this.fp);
- }
+ },
- ,checkIfLoaded: function(r) {
+ checkIfLoaded: function(r) {
r = r || {};
if (this.fp && this.fp.getForm()) { /* so as not to duplicate form */
this.fp.getForm().reset();
@@ -298,48 +709,57 @@ Ext.extend(MODx.Window,Ext.Window,{
return true;
}
return false;
- }
+ },
- ,setValues: function(r) {
+ /* @smg6511:
+ Suggest moving away from using this bulk setValues method and
+ explicitly specifying each field’s value param in window configs,
+ as is done for standard form panel pages. This will already have been done
+ for the element quick create/edit windows. Also the above value-setting
+ procedure in the _loadForm method could be dropped too. All windows in
+ windows.js would need to be updated before dropping.
+ */
+ setValues: function(r) {
if (r === null) { return false; }
this.fp.getForm().setValues(r);
- }
- ,reset: function() {
+ },
+
+ reset: function() {
this.fp.getForm().reset();
- }
+ },
- ,hideField: function(f) {
+ hideField: function(f) {
f.disable();
f.hide();
var d = f.getEl().up('.x-form-item');
if (d) { d.setDisplayed(false); }
- }
+ },
- ,showField: function(f) {
+ showField: function(f) {
f.enable();
f.show();
var d = f.getEl().up('.x-form-item');
if (d) { d.setDisplayed(true); }
- }
+ },
- ,loadDropZones: function() {
- if (this._dzLoaded) return false;
+ loadDropZones: function() {
+ if (this._dzLoaded) { return false; }
var flds = this.fp.getForm().items;
flds.each(function(fld) {
if (fld.isFormField && (
fld.isXType('textfield') || fld.isXType('textarea')
) && !fld.isXType('combo')) {
new MODx.load({
- xtype: 'modx-treedrop'
- ,target: fld
- ,targetEl: fld.getEl().dom
+ xtype: 'modx-treedrop',
+ target: fld,
+ targetEl: fld.getEl().dom
});
}
});
this._dzLoaded = true;
- }
+ },
- ,resizeWindow: function(){
+ resizeWindow: function() {
var viewHeight = Ext.getBody().getViewSize().height;
var el = this.fp.getForm().el;
if(viewHeight < this.originalHeight){
@@ -349,6 +769,210 @@ Ext.extend(MODx.Window,Ext.Window,{
el.setStyle('overflow-y', 'auto');
el.setHeight('auto');
}
+ },
+
+ getWindowIdentifier: function() {
+ return this.itemId || this.ident || this.id || Ext.id();
+ },
+
+ registerPseudomodal: function(window) {
+ const windowId = this.getWindowIdentifier();
+ MODx.openPseudoModals.push({
+ windowId,
+ window
+ });
+ // console.log('registerPseudomodal :: open modals', MODx.openPseudoModals);
+ },
+
+ /**
+ * Removes a pseudomodal window reference from the registry
+ * @param {String} windowId The window's unique identifier
+ */
+ unregisterPseudomodal: function(windowId) {
+ // console.log(`Unegistering pseudomodal with id ${windowId}`);
+ if (!typeof windowId === 'string') {
+ console.error('Aborted unregistering a modal due to an invalid window id being passed.');
+ return;
+ }
+ if (MODx.openPseudoModals.length > 1) {
+ MODx.openPseudoModals.forEach((modxPseudoModal, i) => {
+ if (modxPseudoModal.windowId === windowId) {
+ MODx.openPseudoModals.splice(i, 1);
+ }
+ });
+ // console.log(`Unregistered window (id: ${windowId})\nRemaining modals:`, MODx.openPseudoModals);
+ } else {
+ MODx.openPseudoModals = [];
+ // console.log(`Unregistered only window present (id: ${windowId})`, MODx.openPseudoModals);
+ }
+ },
+
+ // getPseudomodalCount: function() {
+
+ // },
+
+ /**
+ *
+ * @param {*} fbarSwitchFieldName
+ * @param {*} defaultValue
+ */
+ setFbarSwitchHiddenField: function(fbarSwitchFieldName, defaultValue = 1) {
+ // const
+ // windowId = this.getWindowIdentifier(),
+ // switchId = `${windowId}-${fbarSwitchFieldName}`,
+ // switchCmp = Ext.getCmp(switchId)
+ // ;
+ const switchId = `${this.getWindowIdentifier()}-${fbarSwitchFieldName}`;
+ // console.log('switchCmp: ', switchCmp);
+ // if (switchCmp) {
+ // console.log(`Pushing hidden switch cmp for "${switchId}"`);
+ this.config.fields.push({
+ xtype: 'hidden',
+ name: fbarSwitchFieldName,
+ id: `${switchId}-hidden`,
+ value: defaultValue
+ });
+ // }
+ },
+
+ /**
+ *
+ * @param {*} windowId
+ * @param {*} fbarSwitchFieldName
+ * @param {*} switchLabel
+ * @param {*} switchIsChecked
+ * @returns
+ */
+ getFbarSwitch: function(windowId, fbarSwitchFieldName, switchLabel, switchIsChecked = true) {
+ const switchCmp = {
+ xtype: 'xcheckbox',
+ id: `${windowId}-${fbarSwitchFieldName}`,
+ hideLabel: true,
+ boxLabel: switchLabel,
+ inputValue: 1,
+ checked: switchIsChecked,
+ listeners: {
+ check: {
+ fn: function(cmp, checked) {
+ const hiddenCmp = Ext.getCmp(`${windowId}-${fbarSwitchFieldName}-hidden`);
+ // console.log(`fbar hidden id to find: ${windowId}-${fbarSwitchFieldName}-hidden`);
+ // console.log('fbar switch check evt, hiddenCmp', hiddenCmp);
+ if (hiddenCmp) {
+ // console.log('switch is checked?', checked);
+ const value = checked === false ? 0 : 1;
+ hiddenCmp.setValue(value);
+ }
+ },
+ scope: this
+ }
+ }
+ };
+ // console.log(`getting switch (${fbarSwitchFieldName}): `, switchCmp);
+ return switchCmp;
+ },
+
+ /**
+ *
+ * @param {*} config
+ * @param {*} isPrimaryButton
+ * @param {*} isSaveAndClose
+ * @returns
+ */
+ getSaveButton: function(config, isPrimaryButton = true, isSaveAndClose = false) {
+ // console.log('getSaveButton, this', this);
+ const defaultBtnText = isSaveAndClose ? _('save_and_close') : _('save') ;
+ let btn;
+ if (isPrimaryButton) {
+ // console.log('modxFbarButtons: ',config.modxFbarButtons);
+ // console.log('isPrimaryButton, config.saveBtnText: ',config.saveBtnText);
+ // console.log('isPrimaryButton, isSaveAndClose: ',isSaveAndClose);
+ btn = {
+ text: config.saveBtnText || defaultBtnText,
+ cls: 'primary-button',
+ handler: this.submit,
+ scope: this
+ };
+ } else {
+ btn = {
+ text: config.saveBtnText || defaultBtnText,
+ handler: function() {
+ this.submit(false);
+ },
+ scope: this
+ };
+ }
+ // console.log('getSaveButton, btn:', btn);
+ return btn;
+ },
+
+ /**
+ *
+ * @param {*} config
+ * @returns
+ */
+ getWindowButtons: function(config) {
+ const
+ btns = [{
+ text: config.cancelBtnText || _('close'),
+ handler: function() {
+ if (this.config.closeAction !== 'close') {
+ this.hide();
+ } else {
+ this.close();
+ }
+ },
+ scope: this
+ }],
+ specification = config.modxFbarButtons || 'c-s'
+ ;
+ switch (specification) {
+ case 'c-s':
+ btns.push(this.getSaveButton(config));
+ break;
+ case 'c-s-sc':
+ btns.push(this.getSaveButton(config, false));
+ btns.push(this.getSaveButton(config, true, true));
+ break;
+ case 'custom':
+ break;
+ // no default
+ }
+ return btns;
+ },
+
+ /**
+ *
+ * @param {*} config
+ * @returns
+ */
+ getWindowFbar: function(config) {
+ // console.log('getting window fbar...');
+ const
+ windowId = this.getWindowIdentifier(),
+ windowButtons = this.getWindowButtons(config),
+ footerBar = []
+ ;
+ if (config.modxFbarHasClearCacheSwitch) {
+ const cacheSwitch = this.getFbarSwitch(windowId, 'clearCache', _('clear_cache_on_save'));
+ footerBar.push(cacheSwitch);
+ }
+ if (config.modxFbarHasDuplicateValuesSwitch) {
+ const dupValuesSwitch = this.getFbarSwitch(windowId, 'duplicateValues', _('element_duplicate_values'), false);
+ footerBar.push(dupValuesSwitch);
+ }
+ if (config.modxFbarHasRedirectSwitch) {
+ const redirectSwitch = this.getFbarSwitch(windowId, 'redirect', _('duplicate_redirect'), config.redirect);
+ footerBar.push(redirectSwitch);
+ }
+ footerBar.push('->');
+ if (windowButtons && windowButtons.length > 0) {
+ windowButtons.forEach(button => {
+ footerBar.push(button);
+ });
+ }
+
+ return footerBar;
}
+
});
-Ext.reg('modx-window',MODx.Window);
+Ext.reg('modx-window', MODx.Window);
diff --git a/manager/assets/modext/widgets/element/modx.panel.tv.js b/manager/assets/modext/widgets/element/modx.panel.tv.js
index 68480d95677..d6d29b7ff4f 100644
--- a/manager/assets/modext/widgets/element/modx.panel.tv.js
+++ b/manager/assets/modext/widgets/element/modx.panel.tv.js
@@ -110,7 +110,7 @@ MODx.panel.TV = function(config = {}) {
listeners: {
afterrender: {
fn: function(cmp) {
- this.insertTagCopyUtility(cmp, 'tv');
+ MODx.util.insertTagCopyUtility(cmp, 'tv');
},
scope: this
}
@@ -1104,7 +1104,8 @@ Ext.extend(MODx.panel.TVInputProperties, MODx.Panel, {
id: 'modx-tv-elements',
itemId: 'fld-elements',
grow: true,
- maxHeight: 160,
+ growMin: 30,
+ growMax: 200,
value: value,
// eslint-disable-next-line new-parens, no-undef
plugins: new AddFieldUtilities.plugin.Class
diff --git a/manager/assets/modext/widgets/modx.panel.welcome.js b/manager/assets/modext/widgets/modx.panel.welcome.js
index 56f0d81591b..105d45189ab 100644
--- a/manager/assets/modext/widgets/modx.panel.welcome.js
+++ b/manager/assets/modext/widgets/modx.panel.welcome.js
@@ -229,10 +229,9 @@ Ext.extend(MODx.panel.Welcome, MODx.Panel, {
Ext.reg('modx-panel-welcome', MODx.panel.Welcome);
-MODx.window.DashboardWidgetAdd = function (config) {
+MODx.window.DashboardWidgetAdd = function (config = {}) {
+ // console.log('listerners to delete: ', config.listeners);
delete config.listeners;
-
- config = config || {};
this.ident = Ext.id();
Ext.applyIf(config, {
title: _('widget_add'),
@@ -240,17 +239,14 @@ MODx.window.DashboardWidgetAdd = function (config) {
url: MODx.config.connector_url,
baseParams: {
action: 'System/Dashboard/User/Create',
- dashboard: config.dashboard.id,
+ dashboard: config.dashboard.id
},
modal: true,
resizable: false,
collapsible: false,
maximizable: false,
fields: this.getFields(config),
- keys: this.getKeys(config),
- buttons: this.getButtons(config),
- closeAction: 'close',
- success: function () {
+ success: function() {
this._reload = true;
var combo = Ext.getCmp(this.ident + '-widget');
if (combo) {
@@ -275,7 +271,7 @@ Ext.extend(MODx.window.DashboardWidgetAdd, MODx.Window, {
_reload: false,
- getFields: function (config) {
+ getFields: function(config) {
return [{
hideLabel: true,
xtype: 'displayfield',
@@ -316,35 +312,6 @@ Ext.extend(MODx.window.DashboardWidgetAdd, MODx.Window, {
value: 'half',
anchor: '100%'
}];
- },
-
- getButtons: function (config) {
- return [{
- text: config.cancelBtnText || _('cancel'),
- scope: this,
- handler: function () {
- config.closeAction !== 'close'
- ? this.hide()
- : this.close();
- }
- }, {
- text: config.saveBtnText || _('save'),
- cls: 'primary-button',
- scope: this,
- handler: function () {
- this.submit(false);
- },
- }];
- },
-
- getKeys: function () {
- return [{
- key: Ext.EventObject.ENTER,
- shift: true,
- fn: function () {
- this.submit(false);
- }, scope: this
- }];
- },
+ }
});
Ext.reg('modx-window-dashboard-widget-add', MODx.window.DashboardWidgetAdd);
diff --git a/manager/assets/modext/widgets/resource/modx.tree.resource.js b/manager/assets/modext/widgets/resource/modx.tree.resource.js
index 046bf498783..06e3efeea62 100644
--- a/manager/assets/modext/widgets/resource/modx.tree.resource.js
+++ b/manager/assets/modext/widgets/resource/modx.tree.resource.js
@@ -888,6 +888,8 @@ MODx.window.QuickCreateResource = function(config) {
,layout: 'anchor'
,url: MODx.config.connector_url
,action: 'Resource/Create'
+ ,cls: 'qce-window qce-create'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'modx-tabs'
,bodyStyle: { background: 'transparent' }
@@ -1049,6 +1051,7 @@ MODx.window.QuickUpdateResource = function(config) {
title: _('quick_update_resource')
,id: this.ident
,action: 'Resource/Update'
+ ,cls: 'qce-window qce-update'
,buttons: [{
text: config.cancelBtnText || _('cancel')
,scope: this
diff --git a/manager/assets/modext/widgets/resource/modx.window.resource.js b/manager/assets/modext/widgets/resource/modx.window.resource.js
index 66f91b4a4de..a064b3fd0bd 100644
--- a/manager/assets/modext/widgets/resource/modx.window.resource.js
+++ b/manager/assets/modext/widgets/resource/modx.window.resource.js
@@ -65,13 +65,14 @@ MODx.window.CreateResource = function(config = {}) {
});
}
Ext.applyIf(config, {
- autoHeight: true,
+ // autoHeight: true,
title: _('document_new'),
url: MODx.config.connector_url,
baseParams: {
action: 'Resource/Create'
},
width: 600,
+ // cls: 'modx-window qce-window qce-create',
fields: [{
xtype: 'textfield',
fieldLabel: _('resource_pagetitle'),
@@ -321,7 +322,7 @@ Ext.extend(MODx.panel.TemplatePreview, Ext.Panel, {
} else {
this.removeClass('x-form-template-preview-empty');
- var html = '
';
+ var html = '
';
}
this.add({
diff --git a/manager/assets/modext/widgets/system/modx.grid.content.type.js b/manager/assets/modext/widgets/system/modx.grid.content.type.js
index 16bda8b568a..4915f2f2133 100644
--- a/manager/assets/modext/widgets/system/modx.grid.content.type.js
+++ b/manager/assets/modext/widgets/system/modx.grid.content.type.js
@@ -109,29 +109,30 @@ MODx.grid.ContentType = function(config) {
};
Ext.extend(MODx.grid.ContentType,MODx.grid.Grid,{
getMenu: function() {
- var m = [];
+ const m = [];
m.push({
- text: _('edit')
- ,handler: function(btn, e) {
- var window = new MODx.window.CreateContentType({
- record: this.menu.record
- ,title: _('edit')
- ,action: 'System/ContentType/Update'
- ,listeners: {
+ text: _('edit'),
+ handler: function(btn, e) {
+ const window = new MODx.window.CreateContentType({
+ record: this.menu.record,
+ title: _('edit'),
+ action: 'System/ContentType/Update',
+ isUpdate: true,
+ listeners: {
success: {
- fn: this.refresh
- ,scope: this
+ fn: this.refresh,
+ scope: this
}
}
});
window.setRecord(this.menu.record);
window.show(e.target);
- }
- ,scope: this
+ },
+ scope: this
});
m.push({
- text: _('delete')
- ,handler: this.confirm.createDelegate(this,['System/ContentType/Remove',_('content_type_remove_confirm')])
+ text: _('delete'),
+ handler: this.confirm.createDelegate(this, ['System/ContentType/Remove', _('content_type_remove_confirm')])
});
return m;
@@ -163,121 +164,115 @@ Ext.reg('modx-grid-content-type', MODx.grid.ContentType);
* @param {Object} config An object of options.
* @xtype modx-window-content-type-create
*/
-MODx.window.CreateContentType = function(config) {
- config = config || {};
- this.ident = config.ident || 'modx-cct'+Ext.id();
- Ext.applyIf(config,{
- title: _('create')
- ,width: 600
- ,url: MODx.config.connector_url
- ,action: 'System/ContentType/Create'
- ,bwrapCssClass: 'x-window-with-tabs'
- ,fields: [{
- xtype: 'modx-tabs'
- ,items: [{
- title: _('content_type_main_tab')
- ,layout: 'form'
- ,items: [{
- layout: 'column'
- ,border: false
- ,defaults: {
- layout: 'form'
- ,labelAlign: 'top'
- ,anchor: '100%'
- ,border: false
- }
- ,items: [{
- columnWidth: .6
- ,defaults: {
+MODx.window.CreateContentType = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-content-type-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('create'),
+ width: 600,
+ url: MODx.config.connector_url,
+ action: 'System/ContentType/Create',
+ bwrapCssClass: 'x-window-with-tabs',
+ fields: [{
+ xtype: 'modx-tabs',
+ items: [{
+ title: _('content_type_main_tab'),
+ layout: 'form',
+ items: [{
+ layout: 'column',
+ border: false,
+ defaults: {
+ layout: 'form',
+ labelAlign: 'top',
+ anchor: '100%',
+ border: false
+ },
+ items: [{
+ columnWidth: 0.6,
+ defaults: {
msgTarget: 'under'
- }
- ,items: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- fieldLabel: _('name')
- ,name: 'name'
- ,id: this.ident+'-name'
- ,xtype: 'textfield'
- ,anchor: '100%'
- ,allowBlank: false
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: this.ident+'-name'
- ,html: _('name_desc')
- ,cls: 'desc-under'
- },{
- fieldLabel: _('mime_type')
- ,description: MODx.expandHelp ? '' : _('mime_type_desc')
- ,name: 'mime_type'
- ,id: this.ident+'-mime-type'
- ,xtype: 'textfield'
- ,anchor: '100%'
- ,allowBlank: false
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: this.ident+'-mime-type'
- ,html: _('mime_type_desc')
- ,cls: 'desc-under'
+ },
+ items: [{
+ xtype: 'hidden',
+ name: 'id'
+ }, {
+ fieldLabel: _('name'),
+ name: 'name',
+ xtype: 'textfield',
+ anchor: '100%',
+ allowBlank: false
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('name_desc'),
+ cls: 'desc-under'
+ }, {
+ fieldLabel: _('mime_type'),
+ description: MODx.expandHelp ? '' : _('mime_type_desc'),
+ name: 'mime_type',
+ xtype: 'textfield',
+ anchor: '100%',
+ allowBlank: false
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('mime_type_desc'),
+ cls: 'desc-under'
}]
- },{
- columnWidth: .4
- ,defaults: {
+ }, {
+ columnWidth: 0.4,
+ defaults: {
msgTarget: 'under'
- }
- ,items: [{
- fieldLabel: _('icon')
- ,description: MODx.expandHelp ? '' : _('icon_desc')
- ,name: 'icon'
- ,id: this.ident+'-icon'
- ,xtype: 'textfield'
- ,anchor: '100%'
- ,allowBlank: true
- },{
- fieldLabel: _('file_extensions')
- ,description: MODx.expandHelp ? '' : _('file_extensions_desc')
- ,name: 'file_extensions'
- ,id: this.ident+'-file-extensions'
- ,xtype: 'textfield'
- ,anchor: '100%'
- ,allowBlank: true
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: this.ident+'-file-extensions'
- ,html: _('file_extensions_desc')
- ,cls: 'desc-under'
+ },
+ items: [{
+ fieldLabel: _('icon'),
+ description: MODx.expandHelp ? '' : _('icon_desc'),
+ name: 'icon',
+ xtype: 'textfield',
+ anchor: '100%',
+ allowBlank: true
+ }, {
+ fieldLabel: _('file_extensions'),
+ description: MODx.expandHelp ? '' : _('file_extensions_desc'),
+ name: 'file_extensions',
+ xtype: 'textfield',
+ anchor: '100%',
+ allowBlank: true
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('file_extensions_desc'),
+ cls: 'desc-under'
}]
}]
- },{
- xtype: 'xcheckbox'
- ,hideLabel: true
- ,boxLabel: _('binary_desc')
- ,name: 'binary'
- ,hiddenName: 'binary'
- ,id: this.ident+'-binary'
- ,anchor: '100%'
- },{
- fieldLabel: _('description')
- ,name: 'description'
- ,id: 'modx-'+this.ident+'-description'
- ,xtype: 'textarea'
- ,anchor: '100%'
- ,grow: true
- },{
- xtype: 'hidden'
- ,name: 'headers'
+ }, {
+ xtype: 'xcheckbox',
+ hideLabel: true,
+ boxLabel: _('binary_desc'),
+ name: 'binary',
+ hiddenName: 'binary',
+ anchor: '100%'
+ }, {
+ fieldLabel: _('description'),
+ name: 'description',
+ xtype: 'textarea',
+ anchor: '100%',
+ grow: true
+ }, {
+ xtype: 'hidden',
+ name: 'headers'
}]
- },{
- title: _('content_type_header_tab')
- ,layout: 'anchor'
- ,anchor: '100%'
- ,items: [{
- xtype: 'modx-content-type-headers-grid'
- ,id: 'headers'
+ }, {
+ title: _('content_type_header_tab'),
+ layout: 'anchor',
+ anchor: '100%',
+ items: [{
+ xtype: 'modx-content-type-headers-grid',
+ id: 'headers'
}]
}]
- }]
- ,keys: []
+ }],
+ keys: []
});
MODx.window.CreateContentType.superclass.constructor.call(this, config);
diff --git a/manager/assets/modext/widgets/system/modx.tree.directory.js b/manager/assets/modext/widgets/system/modx.tree.directory.js
index 77a154bfc24..39b3221af83 100644
--- a/manager/assets/modext/widgets/system/modx.tree.directory.js
+++ b/manager/assets/modext/widgets/system/modx.tree.directory.js
@@ -781,6 +781,8 @@ MODx.window.CreateDirectory = function(config) {
title: _('file_folder_create')
,url: MODx.config.connector_url
,action: 'Browser/Directory/Create'
+ ,cls: 'qce-window qce-create'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -792,14 +794,12 @@ MODx.window.CreateDirectory = function(config) {
fieldLabel: _('name')
,name: 'name'
,xtype: 'textfield'
- ,anchor: '100%'
,allowBlank: false
},{
fieldLabel: _('file_folder_parent')
,id: 'folder-parent'
,name: 'parent'
,xtype: 'textfield'
- ,anchor: '100%'
},{
xtype: 'label'
,forId: 'folder-parent'
@@ -826,6 +826,8 @@ MODx.window.SetVisibility = function(config) {
title: _('file_folder_visibility')
,url: MODx.config.connector_url
,action: 'Browser/Visibility'
+ ,cls: 'qce-window qce-rename'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -834,23 +836,19 @@ MODx.window.SetVisibility = function(config) {
xtype: 'hidden'
,name: 'source'
},{
- name: 'path'
+ xtype: 'statictextfield'
+ ,name: 'path'
,fieldLabel: _('file_folder_path')
- ,xtype: 'statictextfield'
- ,anchor: '100%'
,submitValue: true
},{
- fieldLabel: _('file_folder_visibility_label')
+ xtype: 'modx-combo-visibility'
,name: 'visibility'
- ,xtype: 'modx-combo-visibility'
- ,anchor: '100%'
+ ,fieldLabel: _('file_folder_visibility_label')
,allowBlank: false
},{
- hideLabel: true
- ,xtype: 'displayfield'
- ,value: _('file_folder_visibility_desc')
- ,anchor: '100%'
- ,allowBlank: false
+ xtype: 'box'
+ ,html: _('file_folder_visibility_desc')
+ ,cls: 'desc-under'
}]
});
MODx.window.SetVisibility.superclass.constructor.call(this,config);
@@ -872,6 +870,8 @@ MODx.window.RenameDirectory = function(config) {
title: _('rename')
,url: MODx.config.connector_url
,action: 'Browser/Directory/Rename'
+ ,cls: 'qce-window qce-rename'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -880,21 +880,18 @@ MODx.window.RenameDirectory = function(config) {
xtype: 'hidden'
,name: 'source'
},{
- fieldLabel: _('path')
+ xtype: 'statictextfield'
,name: 'path'
- ,xtype: 'statictextfield'
+ ,fieldLabel: _('path')
,submitValue: true
- ,anchor: '100%'
},{
- fieldLabel: _('old_name')
+ xtype: 'statictextfield'
,name: 'old_name'
- ,xtype: 'statictextfield'
- ,anchor: '100%'
+ ,fieldLabel: _('old_name')
},{
- fieldLabel: _('new_name')
+ xtype: 'textfield'
,name: 'name'
- ,xtype: 'textfield'
- ,anchor: '100%'
+ ,fieldLabel: _('new_name')
,allowBlank: false
}]
});
@@ -917,6 +914,8 @@ MODx.window.RenameFile = function(config) {
title: _('rename')
,url: MODx.config.connector_url
,action: 'Browser/File/Rename'
+ ,cls: 'qce-window qce-rename'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -925,25 +924,22 @@ MODx.window.RenameFile = function(config) {
xtype: 'hidden'
,name: 'source'
},{
- fieldLabel: _('path')
+ xtype: 'hidden'
+ ,name: 'dir'
+ },{
+ xtype: 'statictextfield'
,name: 'path'
- ,xtype: 'statictextfield'
+ ,fieldLabel: _('path')
,submitValue: true
- ,anchor: '100%'
},{
- fieldLabel: _('old_name')
+ xtype: 'statictextfield'
,name: 'old_name'
- ,xtype: 'statictextfield'
- ,anchor: '100%'
+ ,fieldLabel: _('old_name')
},{
- fieldLabel: _('new_name')
+ xtype: 'textfield'
,name: 'name'
- ,xtype: 'textfield'
- ,anchor: '100%'
+ ,fieldLabel: _('new_name')
,allowBlank: false
- },{
- name: 'dir'
- ,xtype: 'hidden'
}]
});
MODx.window.RenameFile.superclass.constructor.call(this,config);
@@ -964,9 +960,11 @@ MODx.window.QuickUpdateFile = function(config) {
Ext.applyIf(config,{
title: _('file_quick_update')
,width: 600
- ,layout: 'anchor'
+ // ,layout: 'anchor'
,url: MODx.config.connector_url
,action: 'Browser/File/Update'
+ ,cls: 'qce-window qce-update'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -978,21 +976,18 @@ MODx.window.QuickUpdateFile = function(config) {
xtype: 'hidden'
,name: 'file'
},{
- fieldLabel: _('name')
+ xtype: 'statictextfield'
,name: 'name'
- ,xtype: 'statictextfield'
- ,anchor: '100%'
+ ,fieldLabel: _('name')
},{
- fieldLabel: _('path')
+ xtype: 'statictextfield'
,name: 'path'
- ,xtype: 'statictextfield'
- ,anchor: '100%'
+ ,fieldLabel: _('path')
},{
- fieldLabel: _('content')
- ,xtype: 'textarea'
+ xtype: 'textarea'
,name: 'content'
- ,anchor: '100%'
- ,height: 200
+ ,fieldLabel: _('content')
+ ,minGrow: 200
}]
,keys: [{
key: Ext.EventObject.ENTER
@@ -1033,9 +1028,11 @@ MODx.window.QuickCreateFile = function(config) {
Ext.applyIf(config,{
title: _('file_quick_create')
,width: 600
- ,layout: 'anchor'
+ // ,layout: 'anchor'
,url: MODx.config.connector_url
,action: 'Browser/File/Create'
+ ,cls: 'qce-window qce-create'
+ ,modxPseudoModal: true
,fields: [{
xtype: 'hidden'
,name: 'wctx'
@@ -1044,27 +1041,24 @@ MODx.window.QuickCreateFile = function(config) {
xtype: 'hidden'
,name: 'source'
},{
- fieldLabel: _('directory')
+ xtype: 'statictextfield'
,name: 'directory'
+ ,fieldLabel: _('directory')
,submitValue: true
- ,xtype: 'statictextfield'
- ,anchor: '100%'
},{
xtype: 'label'
,html: _('file_folder_parent_desc')
,cls: 'desc-under'
},{
- fieldLabel: _('name')
+ xtype: 'textfield'
,name: 'name'
- ,xtype: 'textfield'
- ,anchor: '100%'
+ ,fieldLabel: _('name')
,allowBlank: false
},{
- fieldLabel: _('content')
- ,xtype: 'textarea'
+ xtype: 'textarea'
,name: 'content'
- ,anchor: '100%'
- ,height: 200
+ ,fieldLabel: _('content')
+ ,minGrow: 200
}]
,keys: [{
key: Ext.EventObject.ENTER
diff --git a/manager/assets/modext/widgets/windows.js b/manager/assets/modext/widgets/windows.js
index f49a7511fbc..7bc4a05eb2e 100644
--- a/manager/assets/modext/widgets/windows.js
+++ b/manager/assets/modext/widgets/windows.js
@@ -6,100 +6,69 @@
* @param {Object} config An object of options.
* @xtype modx-window-resource-duplicate
*/
-MODx.window.DuplicateResource = function(config) {
- config = config || {};
- this.ident = config.ident || 'dupres'+Ext.id();
- Ext.applyIf(config,{
- title: config.pagetitle ? _('duplicate') + ' ' + config.pagetitle : _('duplication_options')
- ,id: this.ident
- });
- MODx.window.DuplicateResource.superclass.constructor.call(this,config);
-};
-Ext.extend(MODx.window.DuplicateResource,MODx.Window,{
- _loadForm: function() {
- if (this.checkIfLoaded(this.config.record)) {
- this.fp.getForm().baseParams = {
- action: 'Resource/Updateduplicate'
- ,prefixDuplicate: true
- ,id: this.config.resource
- };
- return false;
- }
- var items = [];
- items.push({
- xtype: 'textfield'
- ,id: 'modx-'+this.ident+'-name'
- ,fieldLabel: _('resource_name_new')
- ,name: 'name'
- ,anchor: '100%'
- ,value: ''
- });
-
- if (this.config.hasChildren) {
- items.push({
- xtype: 'xcheckbox'
- ,boxLabel: _('duplicate_children') + ' ('+this.config.childCount+')'
- ,hideLabel: true
- ,name: 'duplicate_children'
- ,id: 'modx-'+this.ident+'-duplicate-children'
- ,checked: true
- });
- }
-
- items.push({
- xtype: 'xcheckbox'
- ,boxLabel: _('duplicate_redirect')
- ,hideLabel: true
- ,name: 'redirect'
- ,id: 'modx-'+this.ident+'-duplicate-redirect'
- ,checked: this.config.redirect
- });
-
- var pov = MODx.config.default_duplicate_publish_option || 'preserve';
- items.push({
- xtype: 'fieldset'
- ,title: _('publishing_options')
- ,items: [{
- xtype: 'radiogroup'
- ,hideLabel: true
- ,columns: 1
- ,value: pov
- ,items: [{
- boxLabel: _('po_make_all_unpub')
- ,hideLabel: true
- ,name: 'published_mode'
- ,inputValue: 'unpublish'
- },{
- boxLabel: _('po_make_all_pub')
- ,hideLabel: true
- ,name: 'published_mode'
- ,inputValue: 'publish'
- },{
- boxLabel: _('po_preserve')
- ,hideLabel: true
- ,name: 'published_mode'
- ,inputValue: 'preserve'
+MODx.window.DuplicateResource = function(config = {}) {
+ const
+ publishingOpt = MODx.config.default_duplicate_publish_option || 'preserve',
+ fields = [
+ {
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: _('resource_name_new'),
+ value: ''
+ }, {
+ xtype: 'fieldset',
+ title: _('publishing_options'),
+ items: [{
+ xtype: 'radiogroup',
+ hideLabel: true,
+ columns: 1,
+ value: publishingOpt,
+ items: [{
+ name: 'published_mode',
+ boxLabel: _('po_make_all_unpub'),
+ hideLabel: true,
+ inputValue: 'unpublish'
+ }, {
+ name: 'published_mode',
+ boxLabel: _('po_make_all_pub'),
+ hideLabel: true,
+ inputValue: 'publish'
+ }, {
+ name: 'published_mode',
+ boxLabel: _('po_preserve'),
+ hideLabel: true,
+ inputValue: 'preserve'
+ }]
}]
- }]
- });
-
- this.fp = this.createForm({
- url: this.config.url || MODx.config.connector_url
- ,baseParams: this.config.baseParams || {
- action: 'Resource/Duplicate'
- ,id: this.config.resource
- ,prefixDuplicate: true
}
- ,labelWidth: 125
- ,defaultType: 'textfield'
- ,autoHeight: true
- ,items: items
+ ]
+ ;
+ this.itemId = `resource-duplicate-${Ext.id()}`;
+ if (config.hasChildren) {
+ fields.splice(1, 0, {
+ xtype: 'xcheckbox',
+ name: 'duplicate_children',
+ boxLabel: `${_('duplicate_children')} (${config.childCount})`,
+ hideLabel: true,
+ checked: true
});
-
- this.renderForm();
}
-});
-Ext.reg('modx-window-resource-duplicate',MODx.window.DuplicateResource);
+
+ Ext.applyIf(config, {
+ title: config.pagetitle ? `${_('duplicate')} ${config.pagetitle}` : _('duplication_options'),
+ modxFbarSaveSwitches: ['redirect'],
+ fields: fields,
+ url: config.url || MODx.config.connector_url,
+ baseParams: config.baseParams || {
+ action: 'Resource/Duplicate',
+ id: config.resource,
+ prefixDuplicate: true
+ }
+ });
+ MODx.window.DuplicateResource.superclass.constructor.call(this, config);
+};
+Ext.extend(MODx.window.DuplicateResource, MODx.Window);
+Ext.reg('modx-window-resource-duplicate', MODx.window.DuplicateResource);
/**
* Generates the Duplicate Element window
@@ -109,164 +78,239 @@ Ext.reg('modx-window-resource-duplicate',MODx.window.DuplicateResource);
* @param {Object} config An object of options.
* @xtype modx-window-element-duplicate
*/
-MODx.window.DuplicateElement = function(config) {
- config = config || {};
- this.ident = config.ident || 'dupeel-'+Ext.id();
+MODx.window.DuplicateElement = function(config = {}) {
+ const
+ windowId = `window-dup-${config.record.type || 'element'}-${Ext.id()}`,
+ staticFileCmpId = `${windowId}-modx-static_file`,
+ nameFieldName = config.record.type === 'template' ? 'templatename' : 'name',
+ createExampleTag = ['tv', 'chunk', 'snippet'].includes(config.record.type),
+ defaultExampleTag = createExampleTag ? _(`example_tag_${config.record.type}_name`) : '',
+ elementNameCmpId = `${windowId}-modx-name`,
+ nameFieldListeners = {
+ change: function(cmp) {
+ cmp.setValue(cmp.getValue().trim());
+ }
+ },
+ nameHelpListeners = {}
+ ;
+ this.itemId = windowId;
+ if (createExampleTag) {
+ Object.assign(nameHelpListeners, {
+ afterrender: function(cmp) {
+ MODx.util.insertTagCopyUtility(cmp, config.record.type);
+ }
+ });
+ }
- var flds = [{
- xtype: 'hidden'
- ,name: 'id'
- ,id: 'modx-'+this.ident+'-id'
- },{
- xtype: 'hidden'
- ,name: 'source'
- ,id: 'modx-'+this.ident+'-source'
- },{
- xtype: 'textfield'
- ,fieldLabel: _('element_name_new')
- ,name: config.record.type == 'template' ? 'templatename' : 'name'
- ,id: 'modx-'+this.ident+'-name'
- ,anchor: '100%'
- ,enableKeyEvents: true
- ,listeners: {
- 'afterRender' : {scope:this,fn:function(f,e) {
- this.setStaticElementsPath(f);
- }},
- 'keyup': {scope:this,fn:function(f,e) {
- this.setStaticElementsPath(f);
- }}
- }
+ const fields = [{
+ xtype: 'hidden',
+ name: 'id'
+ }, {
+ xtype: 'hidden',
+ name: 'source'
+ }, {
+ xtype: 'textfield',
+ name: nameFieldName,
+ id: elementNameCmpId,
+ fieldLabel: _(`${config.record.type}_new_name`) || _('element_name_new'),
+ description: MODx.expandHelp ? '' : this.getElementNameDescription(config.record.type, defaultExampleTag, true),
+ enableKeyEvents: true,
+ allowBlank: false,
+ listeners: nameFieldListeners,
+ value: config.record.name
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: MODx.expandHelp ? this.getElementNameDescription(config.record.type, defaultExampleTag) : '',
+ cls: 'desc-under',
+ listeners: nameHelpListeners
}];
- if (config.record.type == 'tv') {
- flds.push({
- xtype: 'textfield'
- ,fieldLabel: _('element_caption_new')
- ,name: 'caption'
- ,id: 'modx-'+this.ident+'-caption'
- ,anchor: '100%'
- });
- flds.push({
- xtype: 'xcheckbox'
- ,hideLabel: true
- ,boxLabel: _('element_duplicate_values')
- ,labelSeparator: ''
- ,name: 'duplicateValues'
- ,id: 'modx-'+this.ident+'-duplicate-values'
- ,anchor: '100%'
- ,inputValue: 1
- ,checked: false
+ if (config.record.type === 'tv') {
+ console.log('TV record being dupd: ', config.record);
+ fields.push({
+ xtype: 'textfield',
+ name: 'caption',
+ fieldLabel: _('tv_new_caption') || _('element_caption_new'),
+ value: config.record.caption
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_caption_desc'),
+ cls: 'desc-under'
});
}
if (config.record.static === true) {
- flds.push({
- xtype: 'textfield'
- ,fieldLabel: _('static_file')
- ,name: 'static_file'
- ,id: 'modx-'+this.ident+'-static_file'
- ,anchor: '100%'
+ fields.push({
+ xtype: 'textfield',
+ name: 'static_file',
+ id: staticFileCmpId,
+ fieldLabel: _('static_file'),
+ listeners: {
+ change: {
+ fn: function(cmp) {
+ // const file = cmp.getValue().trim();
+ // if (!Ext.isEmpty(file)) {
+ // const fileName = cmp.setValue(MODx.util.Format.fileFullPath(file));
+ // }
+ },
+ scope: this
+ }
+ }
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('static_file_desc'),
+ cls: 'desc-under'
});
}
- flds.push({
- xtype: 'xcheckbox'
- ,boxLabel: _('duplicate_redirect')
- ,hideLabel: true
- ,name: 'redirect'
- ,id: 'modx-'+this.ident+'-duplicate-redirect'
- ,checked: config.redirect
- });
-
- Ext.applyIf(config,{
- title: _('duplicate_'+config.record.type)
- ,url: MODx.config.connector_url
- ,action: 'element/'+config.record.type+'/duplicate'
- ,width: 600
- ,fields: flds
- ,labelWidth: 150
+ Ext.applyIf(config, {
+ title: _(`duplicate_${config.record.type}`),
+ url: MODx.config.connector_url,
+ action: `element/${config.record.type}/duplicate`,
+ width: 600,
+ fields: fields,
+ labelWidth: 150,
+ modxFbarSaveSwitches: config.record.type === 'tv' ? ['duplicateValues', 'redirect'] : ['redirect']
});
- MODx.window.DuplicateElement.superclass.constructor.call(this,config);
-};
+ MODx.window.DuplicateElement.superclass.constructor.call(this, config);
-Ext.extend(MODx.window.DuplicateElement,MODx.Window, {
- setStaticElementsPath: function(f) {
- if (this.config.record.static === true) {
- var category = this.config.record.category;
+ if (this.config.record.static) {
+ const
+ elementAutomationType = `${this.config.record.type}s`,
+ staticsAutomationConfigKey = `static_elements_automate_${elementAutomationType}`
+ ;
+ this.staticsAutomated = MODx.config[staticsAutomationConfigKey] ? true : false ;
- if (typeof category !== 'number') {
- if (Ext.getCmp('modx-' + this.config.record.type + '-category').getValue() > 0) {
- category = Ext.getCmp('modx-' + this.config.record.type + '-category').lastSelectionText;
- }
-
- var path = MODx.getStaticElementsPath(f.getValue(), category, this.config.record.type + 's');
- Ext.getCmp('modx-' + this.ident + '-static_file').setValue(path);
- } else {
- // If category is set but is a number, retrieve full category name.
- if (typeof category === "number" && category > 0) {
- MODx.Ajax.request({
- url: MODx.config.connector_url
- ,params: {
- action: 'Element/Category/GetList'
- ,id: category
- }
- ,listeners: {
- 'success': {fn:function(response) {
- for (var i = 0; i < response.results.length; i++) {
- if (response.results[i].id === category) {
- category = response.results[i].name;
- }
+ if (this.staticsAutomated) {
+ const elementCategory = this.config.record.category || 0;
+ this.staticElementType = elementAutomationType;
+ this.getElementCategoryName(elementCategory);
+ } else {
+ const
+ currentPath = this.config.record.static_file,
+ fileName = currentPath.indexOf('/') !== -1 ? currentPath.split('/').pop() : currentPath,
+ fileExt = fileName.indexOf('.') !== -1 ? fileName.slice(fileName.lastIndexOf('.')) : ''
+ ;
+ this.staticElementBasePath = currentPath.replace(fileName, '');
+ this.staticElementFileExt = fileExt;
+ }
+ Ext.getCmp(elementNameCmpId).on({
+ afterrender: {
+ fn: function(cmp) {
+ const elementName = cmp.getValue().trim() || this.config.record.name;
+ let path;
+ if (this.staticsAutomated) {
+ path = MODx.getStaticElementsPath(elementName, this.staticElementCategoryName, this.staticElementType);
+ } else {
+ path = MODx.util.Format.staticElementPathFragment(elementName);
+ path = `${this.staticElementBasePath}${path}${this.staticElementFileExt}`;
+ }
+ Ext.getCmp(staticFileCmpId).setValue(path);
+ },
+ scope: this,
+ delay: 250
+ },
+ keyup: {
+ fn: function(cmp, e) {
+ const elementName = cmp.getValue().trim();
+ let path;
+ if (this.staticsAutomated) {
+ path = MODx.getStaticElementsPath(elementName, this.staticElementCategoryName, this.staticElementType);
+ } else {
+ path = MODx.util.Format.staticElementPathFragment(elementName);
+ path = `${this.staticElementBasePath}${path}${this.staticElementFileExt}`;
+ }
+ Ext.getCmp(staticFileCmpId).setValue(path);
+ },
+ scope: this
+ }
+ });
+ }
+};
+Ext.extend(MODx.window.DuplicateElement, MODx.Window, {
+ /**
+ * Get the Element's category name by its assigned category id (if any)
+ * @param {*} categoryId The category's numeric id
+ */
+ getElementCategoryName: function(categoryId) {
+ if (typeof categoryId === 'number' && categoryId > 0) {
+ MODx.Ajax.request({
+ url: MODx.config.connector_url,
+ params: {
+ action: 'Element/Category/GetList',
+ id: categoryId
+ },
+ listeners: {
+ success: {
+ fn: function(response) {
+ response.results.forEach(result => {
+ if (result.id === categoryId) {
+ this.staticElementCategoryName = result.name;
}
-
- var path = MODx.getStaticElementsPath(f.getValue(), category, this.config.record.type + 's');
- Ext.getCmp('modx-' + this.ident + '-static_file').setValue(path);
- },scope:this}
- }
- });
+ });
+ },
+ scope: this
+ }
}
- }
+ });
+ } else {
+ this.staticElementCategoryName = '';
}
+ },
+ /**
+ * Retrieve a formatted description for an Element's name field
+ * @param {String} elementType The Element's short identifier (i.e., chunk, tv, etc.)
+ * @param {String} defaultExampleTag Pre-formatted MODx tag for placeable Elements (i.e., chunks, snippets, tvs)
+ * @param {Boolean} isCmpDescription Whether the target for the genereated description is the main field component (as opposed to the separate help component shown when MODx.expandHelp is active)
+ * @returns The formatted description
+ */
+ getElementNameDescription: function(elementType, defaultExampleTag = '', isCmpDescription = false) {
+ if (Ext.isEmpty(defaultExampleTag)) {
+ return _(`${elementType}_name_desc`) || '';
+ }
+ return isCmpDescription
+ ? _(`${elementType}_name_desc`, {
+ tag: `[[*${defaultExampleTag}]]`
+ })
+ : _(`${elementType}_name_desc`, {
+ tag: `[[*${defaultExampleTag}]]`
+ })
+ ;
}
});
+Ext.reg('modx-window-element-duplicate', MODx.window.DuplicateElement);
-Ext.reg('modx-window-element-duplicate',MODx.window.DuplicateElement);
-
-MODx.window.CreateCategory = function(config) {
- config = config || {};
- this.ident = config.ident || 'ccat'+Ext.id();
- Ext.applyIf(config,{
- title: _('category_create')
- ,id: this.ident
- ,url: MODx.config.connector_url
- ,action: 'Element/Category/Create'
- ,fields: [{
- xtype: 'modx-description'
- ,html: _('category_create_desc')
- },{
- fieldLabel: _('name')
- ,name: 'category'
- ,id: 'modx-'+this.ident+'-category'
- ,xtype: 'textfield'
- ,anchor: '100%'
- },{
- fieldLabel: _('parent')
- ,name: 'parent'
- ,hiddenName: 'parent'
- ,id: 'modx-'+this.ident+'-parent'
- ,xtype: 'modx-combo-category'
- ,anchor: '100%'
- },{
- fieldLabel: _('rank')
- ,name: 'rank'
- ,id: 'modx-'+this.ident+'-rank'
- ,xtype: 'numberfield'
- ,anchor: '100%'
+MODx.window.CreateCategory = function(config = {}) {
+ this.itemId = `window-create-category-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('category_create'),
+ url: MODx.config.connector_url,
+ action: 'Element/Category/Create',
+ fields: [{
+ xtype: 'modx-description',
+ html: _('category_create_desc')
+ }, {
+ xtype: 'textfield',
+ fieldLabel: _('name'),
+ name: 'category'
+ }, {
+ xtype: 'modx-combo-category',
+ fieldLabel: _('parent'),
+ name: 'parent',
+ hiddenName: 'parent'
+ }, {
+ xtype: 'numberfield',
+ fieldLabel: _('rank'),
+ name: 'rank'
}]
});
- MODx.window.CreateCategory.superclass.constructor.call(this,config);
+ MODx.window.CreateCategory.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.CreateCategory,MODx.Window);
-Ext.reg('modx-window-category-create',MODx.window.CreateCategory);
+Ext.extend(MODx.window.CreateCategory, MODx.Window);
+Ext.reg('modx-window-category-create', MODx.window.CreateCategory);
/**
* Generates the Rename Category window.
@@ -276,686 +320,1091 @@ Ext.reg('modx-window-category-create',MODx.window.CreateCategory);
* @param {Object} config An object of options.
* @xtype modx-window-category-rename
*/
-MODx.window.RenameCategory = function(config) {
- config = config || {};
- this.ident = config.ident || 'rencat-'+Ext.id();
- Ext.applyIf(config,{
- title: _('category_rename')
- ,url: MODx.config.connector_url
- ,action: 'Element/Category/Update'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- ,id: 'modx-'+this.ident+'-id'
- ,value: config.record.id
- },{
- xtype: 'textfield'
- ,fieldLabel: _('name')
- ,name: 'category'
- ,id: 'modx-'+this.ident+'-category'
- ,width: 150
- ,value: config.record.category
- ,anchor: '100%'
- },{
- fieldLabel: _('rank')
- ,name: 'rank'
- ,id: 'modx-'+this.ident+'-rank'
- ,xtype: 'numberfield'
- ,anchor: '100%'
+MODx.window.RenameCategory = function(config = {}) {
+ this.itemId = `window-update-category-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('category_rename'),
+ url: MODx.config.connector_url,
+ action: 'Element/Category/Update',
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id
+ }, {
+ xtype: 'textfield',
+ fieldLabel: _('name'),
+ name: 'category',
+ value: config.record.category
+ }, {
+ xtype: 'numberfield',
+ fieldLabel: _('rank'),
+ name: 'rank'
}]
});
- MODx.window.RenameCategory.superclass.constructor.call(this,config);
+ MODx.window.RenameCategory.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.RenameCategory,MODx.Window);
-Ext.reg('modx-window-category-rename',MODx.window.RenameCategory);
-
+Ext.extend(MODx.window.RenameCategory, MODx.Window);
+Ext.reg('modx-window-category-rename', MODx.window.RenameCategory);
-MODx.window.CreateNamespace = function(config) {
- config = config || {};
- var r = config.record;
- this.ident = config.ident || 'cns'+Ext.id();
- Ext.applyIf(config,{
- title: _('create')
- ,id: this.ident
- ,width: 600
- ,url: MODx.config.connector_url
- ,action: 'Workspace/PackageNamespace/Create'
- ,fields: [{
- xtype: 'textfield'
- ,fieldLabel: _('name')
- ,description: MODx.expandHelp ? '' : _('namespace_name_desc')
- ,name: 'name'
- ,id: 'modx-'+this.ident+'-name'
- ,anchor: '100%'
- ,maxLength: 100
- ,readOnly: config.isUpdate || false
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: 'modx-'+this.ident+'-name'
- ,html: _('namespace_name_desc')
- ,cls: 'desc-under'
-
- },{
- xtype: 'textfield'
- ,fieldLabel: _('namespace_path')
- ,description: MODx.expandHelp ? '' : _('namespace_path_desc')
- ,name: 'path'
- ,id: 'modx-'+this.ident+'-path'
- ,anchor: '100%'
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: 'modx-'+this.ident+'-path'
- ,html: _('namespace_path_desc')
- ,cls: 'desc-under'
-
- },{
- xtype: 'textfield'
- ,fieldLabel: _('namespace_assets_path')
- ,description: MODx.expandHelp ? '' : _('namespace_assets_path_desc')
- ,name: 'assets_path'
- ,id: 'modx-'+this.ident+'-assets-path'
- ,anchor: '100%'
- },{
- xtype: MODx.expandHelp ? 'label' : 'hidden'
- ,forId: 'modx-'+this.ident+'-assets-path'
- ,html: _('namespace_assets_path_desc')
- ,cls: 'desc-under'
+MODx.window.CreateNamespace = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-namespace-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('create'),
+ width: 600,
+ url: MODx.config.connector_url,
+ action: 'Workspace/PackageNamespace/Create',
+ cls: 'qce-window qce-create',
+ fields: [{
+ xtype: 'textfield',
+ fieldLabel: _('name'),
+ description: MODx.expandHelp ? '' : _('namespace_name_desc'),
+ name: 'name',
+ maxLength: 100,
+ readOnly: config.isUpdate || false
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('namespace_name_desc'),
+ cls: 'desc-under'
+ }, {
+ xtype: 'textfield',
+ fieldLabel: _('namespace_path'),
+ description: MODx.expandHelp ? '' : _('namespace_path_desc'),
+ name: 'path'
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('namespace_path_desc'),
+ cls: 'desc-under'
+ }, {
+ xtype: 'textfield',
+ fieldLabel: _('namespace_assets_path'),
+ description: MODx.expandHelp ? '' : _('namespace_assets_path_desc'),
+ name: 'assets_path'
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('namespace_assets_path_desc'),
+ cls: 'desc-under'
}]
});
- MODx.window.CreateNamespace.superclass.constructor.call(this,config);
+ MODx.window.CreateNamespace.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.CreateNamespace,MODx.Window);
-Ext.reg('modx-window-namespace-create',MODx.window.CreateNamespace);
-
-MODx.window.UpdateNamespace = function(config) {
- config = config || {};
+Ext.extend(MODx.window.CreateNamespace, MODx.Window);
+Ext.reg('modx-window-namespace-create', MODx.window.CreateNamespace);
+MODx.window.UpdateNamespace = function(config = {}) {
Ext.applyIf(config, {
- title: _('edit')
- ,action: 'Workspace/PackageNamespace/Update'
- ,isUpdate: true
+ title: _('edit'),
+ action: 'Workspace/PackageNamespace/Update',
+ isUpdate: true
});
MODx.window.UpdateNamespace.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.UpdateNamespace, MODx.window.CreateNamespace, {});
-Ext.reg('modx-window-namespace-update',MODx.window.UpdateNamespace);
-
+Ext.extend(MODx.window.UpdateNamespace, MODx.window.CreateNamespace);
+Ext.reg('modx-window-namespace-update', MODx.window.UpdateNamespace);
-MODx.window.QuickCreateChunk = function(config) {
- config = config || {};
-
- Ext.applyIf(config,{
- title: _('quick_create_chunk')
- ,width: 600
- ,layout: 'anchor'
- ,url: MODx.config.connector_url
- ,action: 'Element/Chunk/Create'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- xtype: 'textfield'
- ,name: 'name'
- ,fieldLabel: _('name')
- ,anchor: '100%'
- },{
- xtype: 'modx-combo-category'
- ,name: 'category'
- ,fieldLabel: _('category')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'description'
- ,fieldLabel: _('description')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'snippet'
- ,fieldLabel: _('code')
- ,anchor: '100%'
- ,grow: true
- ,growMax: 216
- },{
- xtype: 'xcheckbox'
- ,name: 'clearCache'
- ,hideLabel: true
- ,boxLabel: _('clear_cache_on_save')
- ,description: _('clear_cache_on_save_msg')
- ,inputValue: 1
- ,checked: true
- }]
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,shift: true
- ,fn: this.submit
- ,scope: this
+MODx.window.QuickCreateChunk = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-chunk-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('quick_create_chunk'),
+ width: 700,
+ layout: 'form',
+ url: MODx.config.connector_url,
+ action: 'Element/Chunk/Create',
+ cls: 'qce-window qce-create',
+ modxFbarSaveSwitches: ['clearCache'],
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id || 0
+ }, {
+ // row 1
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: _('name'),
+ allowBlank: false,
+ maxLength: 50,
+ value: config.record.name || ''
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-category',
+ name: 'category',
+ fieldLabel: _('category'),
+ description: MODx.expandHelp ? '' : _('chunk_category_desc'),
+ value: config.record.category || 0
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('chunk_category_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 2
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'description',
+ description: MODx.expandHelp ? '' : _('chunk_description_desc'),
+ fieldLabel: _('description'),
+ grow: true,
+ growMin: 50,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.description || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('chunk_description_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 3
+ cls: 'form-row-wrapper',
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top',
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ fieldLabel: _('chunk_code'),
+ name: 'snippet',
+ grow: true,
+ growMin: 90,
+ growMax: this.isSmallScreen ? 160 : 300,
+ value: config.record.snippet || ''
+ }]
+ }],
+ keys: [{
+ key: Ext.EventObject.ENTER,
+ shift: true,
+ fn: this.submit,
+ scope: this
}]
});
- MODx.window.QuickCreateChunk.superclass.constructor.call(this,config);
+ MODx.window.QuickCreateChunk.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickCreateChunk,MODx.Window);
-Ext.reg('modx-window-quick-create-chunk',MODx.window.QuickCreateChunk);
-
-MODx.window.QuickUpdateChunk = function(config) {
- config = config || {};
+Ext.extend(MODx.window.QuickCreateChunk, MODx.Window);
+Ext.reg('modx-window-quick-create-chunk', MODx.window.QuickCreateChunk);
- Ext.applyIf(config,{
- title: _('quick_update_chunk')
- ,action: 'Element/Chunk/Update'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { this.hide(); }
- },{
- text: config.saveBtnText || _('save')
- ,scope: this
- ,handler: function() { this.submit(false); }
- },{
- text: config.saveBtnText || _('save_and_close')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
+MODx.window.QuickUpdateChunk = function(config = {}) {
+ Ext.applyIf(config, {
+ title: _('quick_update_chunk'),
+ action: 'Element/Chunk/Update',
+ cls: 'qce-window qce-update',
+ modxFbarButtons: 'c-s-sc',
+ isUpdate: true
});
- MODx.window.QuickUpdateChunk.superclass.constructor.call(this,config);
+ MODx.window.QuickUpdateChunk.superclass.constructor.call(this, config);
};
Ext.extend(MODx.window.QuickUpdateChunk, MODx.window.QuickCreateChunk);
-Ext.reg('modx-window-quick-update-chunk',MODx.window.QuickUpdateChunk);
+Ext.reg('modx-window-quick-update-chunk', MODx.window.QuickUpdateChunk);
-MODx.window.QuickCreateTemplate = function(config) {
- config = config || {};
-
- Ext.applyIf(config,{
- title: _('quick_create_template')
- ,width: 600
- ,layout: 'anchor'
- ,url: MODx.config.connector_url
- ,action: 'Element/Template/Create'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- xtype: 'textfield'
- ,name: 'templatename'
- ,fieldLabel: _('name')
- ,anchor: '100%'
- },{
- xtype: 'modx-combo-category'
- ,name: 'category'
- ,fieldLabel: _('category')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'description'
- ,fieldLabel: _('description')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'content'
- ,fieldLabel: _('code')
- ,anchor: '100%'
- ,grow: true
- ,growMax: 216
- },{
- xtype: 'xcheckbox'
- ,name: 'clearCache'
- ,hideLabel: true
- ,boxLabel: _('clear_cache_on_save')
- ,description: _('clear_cache_on_save_msg')
- ,inputValue: 1
- ,checked: true
- }]
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,shift: true
- ,fn: this.submit
- ,scope: this
+MODx.window.QuickCreateTemplate = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-template-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('quick_create_template'),
+ width: 700,
+ url: MODx.config.connector_url,
+ action: 'Element/Template/Create',
+ cls: 'qce-window qce-create',
+ modxFbarSaveSwitches: ['clearCache'],
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id || 0
+ }, {
+ // row 1
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'templatename',
+ fieldLabel: _('name'),
+ allowBlank: false,
+ maxLength: 50,
+ value: config.record.templatename || ''
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-category',
+ name: 'category',
+ fieldLabel: _('category'),
+ description: MODx.expandHelp ? '' : _('template_category_desc'),
+ value: config.record.category || 0
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('template_category_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 2
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 1,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'description',
+ description: MODx.expandHelp ? '' : _('template_description_desc'),
+ fieldLabel: _('description'),
+ grow: true,
+ growMin: 50,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.description || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('template_description_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 3
+ cls: 'form-row-wrapper',
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top',
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ fieldLabel: _('template_code'),
+ name: 'content',
+ grow: true,
+ growMin: 120,
+ growMax: this.isSmallScreen ? 160 : 300,
+ value: config.record.content || ''
+ }]
+ }],
+ keys: [{
+ key: Ext.EventObject.ENTER,
+ shift: true,
+ fn: this.submit,
+ scope: this
}]
});
- MODx.window.QuickCreateTemplate.superclass.constructor.call(this,config);
+ MODx.window.QuickCreateTemplate.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickCreateTemplate,MODx.Window);
-Ext.reg('modx-window-quick-create-template',MODx.window.QuickCreateTemplate);
-
-MODx.window.QuickUpdateTemplate = function(config) {
- config = config || {};
+Ext.extend(MODx.window.QuickCreateTemplate, MODx.Window);
+Ext.reg('modx-window-quick-create-template', MODx.window.QuickCreateTemplate);
- Ext.applyIf(config,{
- title: _('quick_update_template')
- ,action: 'Element/Template/Update'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { this.hide(); }
- },{
- text: config.saveBtnText || _('save')
- ,scope: this
- ,handler: function() { this.submit(false); }
- },{
- text: config.saveBtnText || _('save_and_close')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
+MODx.window.QuickUpdateTemplate = function(config = {}) {
+ Ext.applyIf(config, {
+ title: _('quick_update_template'),
+ action: 'Element/Template/Update',
+ cls: 'qce-window qce-update',
+ modxFbarButtons: 'c-s-sc',
+ isUpdate: true
});
- MODx.window.QuickUpdateTemplate.superclass.constructor.call(this,config);
+ MODx.window.QuickUpdateTemplate.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickUpdateTemplate,MODx.window.QuickCreateTemplate);
-Ext.reg('modx-window-quick-update-template',MODx.window.QuickUpdateTemplate);
-
+Ext.extend(MODx.window.QuickUpdateTemplate, MODx.window.QuickCreateTemplate);
+Ext.reg('modx-window-quick-update-template', MODx.window.QuickUpdateTemplate);
-MODx.window.QuickCreateSnippet = function(config) {
- config = config || {};
-
- Ext.applyIf(config,{
- title: _('quick_create_snippet')
- ,width: 600
- ,layout: 'anchor'
- ,url: MODx.config.connector_url
- ,action: 'Element/Snippet/Create'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- xtype: 'textfield'
- ,name: 'name'
- ,fieldLabel: _('name')
- ,anchor: '100%'
- },{
- xtype: 'modx-combo-category'
- ,name: 'category'
- ,fieldLabel: _('category')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'description'
- ,fieldLabel: _('description')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'snippet'
- ,fieldLabel: _('code')
- ,anchor: '100%'
- ,grow: true
- ,growMax: 216
- },{
- xtype: 'xcheckbox'
- ,name: 'clearCache'
- ,hideLabel: true
- ,boxLabel: _('clear_cache_on_save')
- ,description: _('clear_cache_on_save_msg')
- ,inputValue: 1
- ,checked: true
- }]
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,shift: true
- ,fn: this.submit
- ,scope: this
+MODx.window.QuickCreateSnippet = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-snippet-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('quick_create_snippet'),
+ width: 700,
+ url: MODx.config.connector_url,
+ action: 'Element/Snippet/Create',
+ cls: 'qce-window qce-create',
+ modxPseudoModal: true,
+ modxFbarSaveSwitches: ['clearCache'],
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id || 0
+ }, {
+ // row 1
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: _('name'),
+ allowBlank: false,
+ maxLength: 50,
+ value: config.record.name || ''
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-category',
+ name: 'category',
+ fieldLabel: _('category'),
+ description: MODx.expandHelp ? '' : _('snippet_category_desc'),
+ value: config.record.category || 0
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('snippet_category_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 2
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 1,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'description',
+ description: MODx.expandHelp ? '' : _('snippet_description_desc'),
+ fieldLabel: _('description'),
+ grow: true,
+ growMin: 50,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.description || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('snippet_description_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 3
+ cls: 'form-row-wrapper',
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top',
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ fieldLabel: _('snippet_code'),
+ name: 'snippet',
+ id: `modx-${this.ident}-code`,
+ grow: true,
+ growMin: 90,
+ growMax: this.isSmallScreen ? 160 : 300,
+ value: config.record.snippet || ''
+ }]
+ }],
+ keys: [{
+ key: Ext.EventObject.ENTER,
+ shift: true,
+ fn: this.submit,
+ scope: this
}]
});
- MODx.window.QuickCreateSnippet.superclass.constructor.call(this,config);
+ MODx.window.QuickCreateSnippet.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickCreateSnippet,MODx.Window);
-Ext.reg('modx-window-quick-create-snippet',MODx.window.QuickCreateSnippet);
-
-MODx.window.QuickUpdateSnippet = function(config) {
- config = config || {};
+Ext.extend(MODx.window.QuickCreateSnippet, MODx.Window);
+Ext.reg('modx-window-quick-create-snippet', MODx.window.QuickCreateSnippet);
- Ext.applyIf(config,{
- title: _('quick_update_snippet')
- ,action: 'Element/Snippet/Update'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { this.hide(); }
- },{
- text: config.saveBtnText || _('save')
- ,scope: this
- ,handler: function() { this.submit(false); }
- },{
- text: config.saveBtnText || _('save_and_close')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
+MODx.window.QuickUpdateSnippet = function(config = {}) {
+ Ext.applyIf(config, {
+ title: _('quick_update_snippet'),
+ action: 'Element/Snippet/Update',
+ cls: 'qce-window qce-update',
+ modxFbarButtons: 'c-s-sc',
+ isUpdate: true
});
- MODx.window.QuickUpdateSnippet.superclass.constructor.call(this,config);
+ MODx.window.QuickUpdateSnippet.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickUpdateSnippet,MODx.window.QuickCreateSnippet);
-Ext.reg('modx-window-quick-update-snippet',MODx.window.QuickUpdateSnippet);
-
-
-
-MODx.window.QuickCreatePlugin = function(config) {
- config = config || {};
+Ext.extend(MODx.window.QuickUpdateSnippet, MODx.window.QuickCreateSnippet);
+Ext.reg('modx-window-quick-update-snippet', MODx.window.QuickUpdateSnippet);
- Ext.applyIf(config,{
- title: _('quick_create_plugin')
- ,width: 600
- ,layout: 'anchor'
- ,url: MODx.config.connector_url
- ,action: 'Element/Plugin/Create'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- xtype: 'textfield'
- ,name: 'name'
- ,fieldLabel: _('name')
- ,anchor: '100%'
- },{
- xtype: 'modx-combo-category'
- ,name: 'category'
- ,fieldLabel: _('category')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'description'
- ,fieldLabel: _('description')
- ,anchor: '100%'
- ,rows: 2
- },{
- xtype: 'textarea'
- ,name: 'plugincode'
- ,fieldLabel: _('code')
- ,anchor: '100%'
- ,grow: true
- ,growMax: 216
- },{
- xtype: 'xcheckbox'
- ,name: 'disabled'
- ,boxLabel: _('disabled')
- ,hideLabel: true
- ,inputValue: 1
- ,checked: false
- },{
- xtype: 'xcheckbox'
- ,name: 'clearCache'
- ,boxLabel: _('clear_cache_on_save')
- ,hideLabel: true
- ,description: _('clear_cache_on_save_msg')
- ,inputValue: 1
- ,checked: true
- }]
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,shift: true
- ,fn: this.submit
- ,scope: this
+MODx.window.QuickCreatePlugin = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-plugin-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('quick_create_plugin'),
+ width: 700,
+ layout: 'anchor',
+ url: MODx.config.connector_url,
+ action: 'Element/Plugin/Create',
+ modxPseudoModal: true,
+ modxFbarSaveSwitches: ['clearCache'],
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id || 0
+ }, {
+ // row 1
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: _('name'),
+ allowBlank: false,
+ maxLength: 50,
+ value: config.record.name || ''
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-category',
+ name: 'category',
+ fieldLabel: _('category'),
+ description: MODx.expandHelp ? '' : _('plugin_category_desc'),
+ value: config.record.category || 0
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('plugin_category_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 2
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'description',
+ description: MODx.expandHelp ? '' : _('plugin_description_desc'),
+ fieldLabel: _('description'),
+ grow: true,
+ growMin: 50,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.description || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('plugin_description_desc'),
+ cls: 'desc-under'
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'xcheckbox',
+ name: 'disabled',
+ hideLabel: true,
+ boxLabel: _('plugin_disabled'),
+ description: MODx.expandHelp ? '' : _('plugin_disabled_desc'),
+ ctCls: 'add-label-space',
+ inputValue: 1,
+ checked: config.record.disabled || 0
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('plugin_disabled_desc'),
+ cls: 'desc-under toggle-slider-above'
+ }]
+ }]
+ }]
+ }, {
+ // row 3
+ cls: 'form-row-wrapper',
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top',
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ fieldLabel: _('plugin_code'),
+ name: 'plugincode',
+ grow: true,
+ growMin: 90,
+ growMax: this.isSmallScreen ? 160 : 300,
+ value: config.record.plugincode || ''
+ }]
+ }],
+ keys: [{
+ key: Ext.EventObject.ENTER,
+ shift: true,
+ fn: this.submit,
+ scope: this
}]
});
- MODx.window.QuickCreatePlugin.superclass.constructor.call(this,config);
+ MODx.window.QuickCreatePlugin.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickCreatePlugin,MODx.Window);
-Ext.reg('modx-window-quick-create-plugin',MODx.window.QuickCreatePlugin);
+Ext.extend(MODx.window.QuickCreatePlugin, MODx.Window);
+Ext.reg('modx-window-quick-create-plugin', MODx.window.QuickCreatePlugin);
-MODx.window.QuickUpdatePlugin = function(config) {
- config = config || {};
-
- Ext.applyIf(config,{
- title: _('quick_update_plugin')
- ,action: 'Element/Plugin/Update'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { this.hide(); }
- },{
- text: config.saveBtnText || _('save')
- ,scope: this
- ,handler: function() { this.submit(false); }
- },{
- text: config.saveBtnText || _('save_and_close')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
+MODx.window.QuickUpdatePlugin = function(config = {}) {
+ Ext.applyIf(config, {
+ title: _('quick_update_plugin'),
+ action: 'Element/Plugin/Update',
+ cls: 'qce-window qce-update',
+ modxFbarButtons: 'c-s-sc',
+ isUpdate: true
});
- MODx.window.QuickUpdatePlugin.superclass.constructor.call(this,config);
+ MODx.window.QuickUpdatePlugin.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickUpdatePlugin,MODx.window.QuickCreatePlugin);
-Ext.reg('modx-window-quick-update-plugin',MODx.window.QuickUpdatePlugin);
-
+Ext.extend(MODx.window.QuickUpdatePlugin, MODx.window.QuickCreatePlugin);
+Ext.reg('modx-window-quick-update-plugin', MODx.window.QuickUpdatePlugin);
-MODx.window.QuickCreateTV = function(config) {
- config = config || {};
- this.ident = config.ident || 'qtv'+Ext.id();
-
- Ext.applyIf(config,{
- title: _('quick_create_tv')
- ,width: 700
- ,url: MODx.config.connector_url
- ,action: 'Element/TemplateVar/Create'
- ,fields: [{
- xtype: 'hidden'
- ,name: 'id'
- },{
- layout: 'column'
- ,border: false
- ,items: [{
- columnWidth: .6
- ,layout: 'form'
- ,items: [{
- xtype: 'textfield'
- ,name: 'name'
- ,fieldLabel: _('name')
- ,anchor: '100%'
- },{
- xtype: 'textfield'
- ,name: 'caption'
- ,id: 'modx-'+this.ident+'-caption'
- ,fieldLabel: _('caption')
- ,anchor: '100%'
- },{
- xtype: 'label'
- ,forId: 'modx-'+this.ident+'-caption'
- ,html: _('caption_desc')
- ,cls: 'desc-under'
- },{
- xtype: 'modx-combo-category'
- ,name: 'category'
- ,fieldLabel: _('category')
- ,anchor: '100%'
- },{
- xtype: 'textarea'
- ,name: 'description'
- ,fieldLabel: _('description')
- ,anchor: '100%'
+MODx.window.QuickCreateTV = function(config = {}) {
+ const action = config.isUpdate ? 'update' : 'create';
+ this.itemId = `window-tv-${action}-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('quick_create_tv'),
+ width: 640,
+ url: MODx.config.connector_url,
+ action: 'Element/TemplateVar/Create',
+ cls: 'qce-window qce-create',
+ modxFbarSaveSwitches: ['clearCache'],
+ fields: [{
+ xtype: 'hidden',
+ name: 'id',
+ value: config.record.id || 0
+ }, {
+ // row 1
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: _('name'),
+ description: MODx.expandHelp ? '' : _('tv_name_desc', {
+ tag: `[[*${_('example_tag_tv_name')}]]`
+ }),
+ allowBlank: false,
+ maxLength: 50,
+ value: config.record.name || '',
+ enableKeyEvents: true,
+ listeners: {
+ keyup: {
+ fn: function(cmp, e) {
+ let title = Ext.util.Format.stripTags(cmp.getValue()),
+ tagTitle
+ ;
+ title = Ext.util.Format.htmlEncode(title);
+ tagTitle = title.length > 0 ? title : _('example_tag_tv_name');
+ cmp.nextSibling().getEl().child('.example-replace-name').update(tagTitle);
+ },
+ scope: this
+ }
+ }
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_name_desc', {
+ tag: `[[*${_('example_tag_tv_name')}]]`
+ }),
+ cls: 'desc-under',
+ listeners: {
+ afterrender: {
+ fn: function(cmp) {
+ MODx.util.insertTagCopyUtility(cmp, 'tv');
+ },
+ scope: this
+ }
+ }
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-tv-input-type',
+ fieldLabel: _('tv_type'),
+ name: 'type',
+ value: config.record.type || 'text'
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_type_desc'),
+ cls: 'desc-under'
+ }]
}]
- },{
- columnWidth: .4
- ,border: false
- ,layout: 'form'
- ,items: [{
- xtype: 'modx-combo-tv-input-type'
- ,fieldLabel: _('tv_type')
- ,name: 'type'
- ,anchor: '100%'
- },{
- xtype: 'textfield'
- ,fieldLabel: _('tv_elements')
- ,name: 'els'
- ,id: 'modx-'+this.ident+'-elements'
- ,anchor: '100%'
- },{
- xtype: 'label'
- ,forId: 'modx-'+this.ident+'-elements'
- ,html: _('tv_elements_short_desc')
- ,cls: 'desc-under'
- },{
- xtype: 'textarea'
- ,fieldLabel: _('tv_default')
- ,name: 'default_text'
- ,id: 'modx-'+this.ident+'-default-text'
- ,anchor: '100%'
- ,grow: true
- ,growMax: Ext.getBody().getViewSize().height <= 768 ? 300 : 380
- },{
- xtype: 'label'
- ,forId: 'modx-'+this.ident+'-default-text'
- ,html: _('tv_default_desc')
- ,cls: 'desc-under'
+ }]
+ }, {
+ // row 2
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textfield',
+ name: 'caption',
+ fieldLabel: _('caption'),
+ description: MODx.expandHelp ? '' : _('tv_caption_desc'),
+ maxLength: 50,
+ value: config.record.caption || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_caption_desc'),
+ cls: 'desc-under'
+ }]
+ }, {
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under'
+ },
+ items: [{
+ xtype: 'modx-combo-category',
+ name: 'category',
+ fieldLabel: _('category'),
+ description: MODx.expandHelp ? '' : _('tv_category_desc'),
+ value: config.record.category || 0
+ }, {
+ xtype: MODx.expandHelp ? 'box' : 'hidden',
+ html: _('tv_category_desc'),
+ cls: 'desc-under'
+ }]
}]
}]
- },{
- xtype: 'xcheckbox'
- ,name: 'clearCache'
- ,hideLabel: true
- ,boxLabel: _('clear_cache_on_save')
- ,description: _('clear_cache_on_save_msg')
- ,inputValue: 1
- ,checked: true
- }]
- ,keys: [{
- key: Ext.EventObject.ENTER
- ,shift: true
- ,fn: this.submit
- ,scope: this
+ }, {
+ // row 3
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 1,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'description',
+ fieldLabel: _('description'),
+ description: MODx.expandHelp ? '' : _('tv_description_desc'),
+ grow: true,
+ growMin: 30,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.description || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_description_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 4
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 1,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'els',
+ fieldLabel: _('tv_elements'),
+ description: MODx.expandHelp ? '' : _('tv_elements_short_desc'),
+ grow: true,
+ growMin: 30,
+ growMax: this.isSmallScreen ? 90 : 120,
+ value: config.record.els || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_elements_short_desc'),
+ cls: 'desc-under'
+ }]
+ }]
+ }]
+ }, {
+ // row 5
+ cls: 'form-row-wrapper',
+ defaults: {
+ layout: 'column'
+ },
+ items: [{
+ defaults: {
+ layout: 'form',
+ labelSeparator: '',
+ labelAlign: 'top'
+ },
+ items: [{
+ columnWidth: 0.5,
+ defaults: {
+ anchor: '100%',
+ msgTarget: 'under',
+ validationEvent: 'change',
+ validateOnBlur: false
+ },
+ items: [{
+ xtype: 'textarea',
+ name: 'default_text',
+ fieldLabel: _('tv_default'),
+ description: MODx.expandHelp ? '' : _('tv_default_desc'),
+ grow: true,
+ growMin: 30,
+ growMax: 60,
+ value: config.record.default_text || ''
+ }, {
+ xtype: 'box',
+ hidden: !MODx.expandHelp,
+ html: _('tv_default_desc'),
+ cls: 'desc-under'
+ }]
+ }, {
+ // using empty column here to allow full-width of previous column in mobile contexts
+ columnWidth: 0.5,
+ items: []
+ }]
+ }]
+ }],
+ keys: [{
+ key: Ext.EventObject.ENTER,
+ shift: true,
+ fn: this.submit,
+ scope: this
}]
});
- MODx.window.QuickCreateTV.superclass.constructor.call(this,config);
+ MODx.window.QuickCreateTV.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickCreateTV,MODx.Window);
-Ext.reg('modx-window-quick-create-tv',MODx.window.QuickCreateTV);
+Ext.extend(MODx.window.QuickCreateTV, MODx.Window);
+Ext.reg('modx-window-quick-create-tv', MODx.window.QuickCreateTV);
-MODx.window.QuickUpdateTV = function(config) {
- config = config || {};
-
- Ext.applyIf(config,{
- title: _('quick_update_tv')
- ,action: 'Element/TemplateVar/Update'
- ,buttons: [{
- text: config.cancelBtnText || _('cancel')
- ,scope: this
- ,handler: function() { this.hide(); }
- },{
- text: config.saveBtnText || _('save')
- ,scope: this
- ,handler: function() { this.submit(false); }
- },{
- text: config.saveBtnText || _('save_and_close')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
- }]
+MODx.window.QuickUpdateTV = function(config = {}) {
+ Ext.applyIf(config, {
+ title: _('quick_update_tv'),
+ action: 'Element/TemplateVar/Update',
+ cls: 'qce-window qce-update',
+ modxFbarButtons: 'c-s-sc',
+ isUpdate: true
});
- MODx.window.QuickUpdateTV.superclass.constructor.call(this,config);
+ MODx.window.QuickUpdateTV.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.QuickUpdateTV,MODx.window.QuickCreateTV);
-Ext.reg('modx-window-quick-update-tv',MODx.window.QuickUpdateTV);
+Ext.extend(MODx.window.QuickUpdateTV, MODx.window.QuickCreateTV);
+Ext.reg('modx-window-quick-update-tv', MODx.window.QuickUpdateTV);
-
-MODx.window.DuplicateContext = function(config) {
- config = config || {};
- this.ident = config.ident || 'dupctx'+Ext.id();
+MODx.window.DuplicateContext = function(config = {}) {
Ext.Ajax.timeout = 0;
- Ext.applyIf(config,{
- title: _('duplicate')
- ,id: this.ident
- ,url: MODx.config.connector_url
- ,action: 'Context/Duplicate'
- ,fields: [{
- xtype: 'statictextfield'
- ,id: 'modx-'+this.ident+'-key'
- ,fieldLabel: _('old_key')
- ,name: 'key'
- ,anchor: '100%'
- ,submitValue: true
- },{
- xtype: 'textfield'
- ,id: 'modx-'+this.ident+'-newkey'
- ,fieldLabel: _('new_key')
- ,name: 'newkey'
- ,anchor: '100%'
- ,value: ''
- },{
- xtype: 'checkbox'
- ,id: 'modx-'+this.ident+'-preserveresources'
- ,hideLabel: true
- ,boxLabel: _('preserve_resources')
- ,name: 'preserve_resources'
- ,anchor: '100%'
- ,checked: true
- ,listeners: {
- 'check': {fn: function(cb,checked) {
- if (checked) {
- this.fp.getForm().findField('modx-'+this.ident+'-preservealias').setValue(true).enable();
- this.fp.getForm().findField('modx-'+this.ident+'-preservemenuindex').setValue(true).enable();
- } else {
- this.fp.getForm().findField('modx-'+this.ident+'-preservealias').setValue(false).disable();
- this.fp.getForm().findField('modx-'+this.ident+'-preservemenuindex').setValue(false).disable();
- }
- },scope:this}
+ const
+ windowId = `window-dup-context-${Ext.id()}`,
+ preserveAliasCmpId = `${windowId}-modx-preserve_alias`,
+ preserveMenuIndexCmpId = `${windowId}-modx-preserve_menuindex`
+ ;
+ this.itemId = windowId;
+ Ext.applyIf(config, {
+ title: _('duplicate'),
+ url: MODx.config.connector_url,
+ action: 'Context/Duplicate',
+ fields: [{
+ xtype: 'statictextfield',
+ name: 'key',
+ fieldLabel: _('old_key'),
+ submitValue: true
+ }, {
+ xtype: 'textfield',
+ name: 'newkey',
+ fieldLabel: _('new_key'),
+ value: ''
+ }, {
+ xtype: 'checkbox',
+ name: 'preserve_resources',
+ hideLabel: true,
+ boxLabel: _('preserve_resources'),
+ checked: true,
+ listeners: {
+ check: {
+ fn: function(cb, checked) {
+ const form = this.fp.getForm();
+ if (checked) {
+ form.findField(preserveAliasCmpId).setValue(true).enable();
+ form.findField(preserveMenuIndexCmpId).setValue(true).enable();
+ } else {
+ form.findField(preserveAliasCmpId).setValue(false).disable();
+ form.findField(preserveMenuIndexCmpId).setValue(false).disable();
+ }
+ },
+ scope: this
+ }
}
-
- },{
- xtype: 'checkbox'
- ,id: 'modx-'+this.ident+'-preservealias'
- ,hideLabel: true
- ,boxLabel: _('preserve_alias') // Todo: add translation
- ,name: 'preserve_alias'
- ,anchor: '100%'
- ,checked: true
- },{
- xtype: 'checkbox'
- ,id: 'modx-'+this.ident+'-preservemenuindex'
- ,hideLabel: true
- ,boxLabel: _('preserve_menuindex') // Todo: add translation
- ,name: 'preserve_menuindex'
- ,anchor: '100%'
- ,checked: true
+ }, {
+ xtype: 'checkbox',
+ id: preserveAliasCmpId,
+ name: 'preserve_alias',
+ hideLabel: true,
+ boxLabel: _('preserve_alias'),
+ checked: true
+ }, {
+ xtype: 'checkbox',
+ id: preserveMenuIndexCmpId,
+ name: 'preserve_menuindex',
+ hideLabel: true,
+ boxLabel: _('preserve_menuindex'),
+ checked: true
}]
});
- MODx.window.DuplicateContext.superclass.constructor.call(this,config);
+ MODx.window.DuplicateContext.superclass.constructor.call(this, config);
};
-Ext.extend(MODx.window.DuplicateContext,MODx.Window);
-Ext.reg('modx-window-context-duplicate',MODx.window.DuplicateContext);
+Ext.extend(MODx.window.DuplicateContext, MODx.Window);
+Ext.reg('modx-window-context-duplicate', MODx.window.DuplicateContext);
-MODx.window.Login = function(config) {
- config = config || {};
- this.ident = config.ident || 'dupctx'+Ext.id();
+MODx.window.Login = function(config = {}) {
Ext.Ajax.timeout = 0;
- Ext.applyIf(config,{
- title: _('login')
- ,id: this.ident
- ,url: MODx.config.connectors_url
- ,action: 'Security/Login'
- ,fields: [{
- html: ''+_('session_logging_out')+'
'
- ,xtype: 'modx-description'
- },{
- xtype: 'textfield'
- ,id: 'modx-'+this.ident+'-username'
- ,fieldLabel: _('username')
- ,name: 'username'
- ,anchor: '100%'
- },{
- xtype: 'textfield'
- ,inputType: 'password'
- ,id: 'modx-'+this.ident+'-password'
- ,fieldLabel: _('password')
- ,name: 'password'
- ,anchor: '100%'
- },{
- xtype: 'hidden'
- ,name: 'rememberme'
- ,value: 1
- }]
- ,buttons: [{
- text: _('logout')
- ,scope: this
- ,handler: function() { location.href = '?logout=1' }
- },{
- text: _('login')
- ,cls: 'primary-button'
- ,scope: this
- ,handler: this.submit
+ this.itemId = `window-login-extend-${Ext.id()}`;
+ Ext.applyIf(config, {
+ title: _('login'),
+ url: MODx.config.connectors_url,
+ action: 'Security/Login',
+ fields: [{
+ html: `${_('session_logging_out')}
`,
+ xtype: 'modx-description'
+ }, {
+ xtype: 'textfield',
+ name: 'username',
+ fieldLabel: _('username')
+ }, {
+ xtype: 'textfield',
+ name: 'password',
+ inputType: 'password',
+ fieldLabel: _('password')
+ }, {
+ xtype: 'hidden',
+ name: 'rememberme',
+ value: 1
+ }],
+ buttons: [{
+ text: _('logout'),
+ scope: this,
+ handler: function() {
+ window.location.href = '?logout=1';
+ }
+ }, {
+ text: _('login'),
+ cls: 'primary-button',
+ scope: this,
+ handler: this.submit
}]
});
- MODx.window.Login.superclass.constructor.call(this,config);
- this.on('success',this.onLogin,this);
+ MODx.window.Login.superclass.constructor.call(this, config);
+ this.on('success', this.onLogin, this);
};
-Ext.extend(MODx.window.Login,MODx.Window,{
+Ext.extend(MODx.window.Login, MODx.Window, {
onLogin: function(o) {
var r = o.a.result;
if (r.object && r.object.token) {
@@ -972,4 +1421,82 @@ Ext.extend(MODx.window.Login,MODx.Window,{
}
}
});
-Ext.reg('modx-window-login',MODx.window.Login);
+Ext.reg('modx-window-login', MODx.window.Login);
+
+MODx.window.SaveProgress = function(config = {}) {
+ this.uniqueId = Ext.id();
+ Ext.applyIf(config, {
+ title: _('please_wait'),
+ modal: true,
+ id: `modx-window-saveprogress-modal-${this.uniqueId}`,
+ modxPseudoModal: false,
+ width: 300,
+ minimizable: false,
+ maximizable: false,
+ closable: false,
+ collapsible: false,
+ draggable: false,
+ resizable: false,
+ cls: 'x-window-dlg x-window-plain',
+ items: [
+ {
+ id: `modx-window-status-progress-text-${this.uniqueId}`,
+ xtype: 'box',
+ html: config.progressStartText || _('saving')
+ },
+ this.setProgressBar()
+ ],
+ fbar: [],
+ tools: [],
+ listeners: {
+ show: {
+ fn: function() {
+ this.setProgressStart();
+ }
+ }
+ }
+ });
+ MODx.window.SaveProgress.superclass.constructor.call(this, config);
+ this.config = config;
+};
+Ext.extend(MODx.window.SaveProgress, MODx.Window, {
+ init: function() {
+ this.show();
+ },
+ exit: function(exitStatus = 'success') {
+ if (exitStatus === 'success') {
+ this.setProgressDone();
+ setTimeout(() => {
+ this.close();
+ this.destroy();
+ }, this.config.exitDelay || 150);
+ } else {
+ this.close();
+ }
+ },
+ setProgressBar: function() {
+ return new Ext.ProgressBar({
+ id: 'modx-window-status-progressbar'
+ });
+ },
+ setProgressStart: function() {
+ this.progressBar = Ext.getCmp('modx-window-status-progressbar').wait({
+ interval: 200,
+ increment: 20
+ });
+ },
+ setProgressDone: function() {
+ this.progressBar.reset();
+ Ext.fly(`modx-window-status-progress-text-${this.uniqueId}`).update(`${_('done')}!`);
+ },
+ /**
+ * Override private onWindowResize method to avoid resize error when modal is
+ * destroyed; this should generally never run for this type of window anyway
+ */
+ onWindowResize: function() {
+ if (!this.isDestroyed) {
+ this.prototype.onWindowResize();
+ }
+ }
+});
+Ext.reg('modx-window-saveprogress', MODx.window.SaveProgress);
diff --git a/manager/templates/default/header.tpl b/manager/templates/default/header.tpl
index 30d36631f67..3e898ca9df8 100644
--- a/manager/templates/default/header.tpl
+++ b/manager/templates/default/header.tpl
@@ -24,7 +24,9 @@
{$maincssjs}
@@ -34,6 +36,7 @@
+