diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index b361ea112..2427f87ce 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -13467,6 +13467,9 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { top: 8px; z-index: 2000; } +.control-treeview { + margin-bottom: 40px; +} .control-treeview ol { margin: 0; padding: 0; @@ -13480,12 +13483,18 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { .control-treeview ol > li > div { font-size: 13px; font-weight: 600; - color: #2b3e50; background: #ffffff; border-bottom: 1px solid #ecf0f1; - padding: 16px 16px 15px 61px; position: relative; - cursor: pointer; +} +.control-treeview ol > li > div > a { + color: #2b3e50; + padding: 16px 45px 15px 61px; + display: block; + text-decoration: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } .control-treeview ol > li > div:before { content: ' '; @@ -13549,7 +13558,7 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { -webkit-transition: opacity 0.4s; transition: opacity 0.4s; position: absolute; - right: 10px; + right: 24px; bottom: 5px; width: 14px; height: 14px; @@ -13571,9 +13580,68 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { .control-treeview ol > li > div span.borders { font-size: 0; } +.control-treeview ol > li > div > ul.submenu { + position: absolute; + left: 20px; + bottom: -37px; + padding: 0; + list-style: none; + z-index: 200; + height: 37px; + display: none; + margin-left: 15px; + background: transparent url(../images/treeview-submenu-tabs.png) repeat-x left -39px; +} +.control-treeview ol > li > div > ul.submenu:before, +.control-treeview ol > li > div > ul.submenu:after { + background: transparent url(../images/treeview-submenu-tabs.png) no-repeat left top; + content: ' '; + display: block; + width: 20px; + height: 37px; + position: absolute; + top: 0; +} +.control-treeview ol > li > div > ul.submenu:before { + left: -20px; +} +.control-treeview ol > li > div > ul.submenu:after { + background-position: -100px top; + right: -20px; +} +.control-treeview ol > li > div > ul.submenu li { + font-size: 12px; +} +.control-treeview ol > li > div > ul.submenu li a { + display: block; + padding: 4px 3px 0 3px; + color: #ffffff; + text-decoration: none; + outline: none; +} +.control-treeview ol > li > div > ul.submenu li a i { + margin-right: 5px; +} +.control-treeview ol > li > div:hover > ul.submenu { + display: block; +} +.control-treeview ol > li > div .checkbox { + position: absolute; + top: 19px; + right: 17px; +} +.control-treeview ol > li > div .checkbox label { + margin-right: 0; +} +.control-treeview ol > li > div .checkbox label:before { + border-color: #cccccc; +} .control-treeview ol > li.dragged div, .control-treeview ol > li > div:hover { background-color: #58b6f7 !important; +} +.control-treeview ol > li.dragged div > a, +.control-treeview ol > li > div:hover > a { color: #ffffff !important; } .control-treeview ol > li.dragged div:before, @@ -13603,6 +13671,9 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { .control-treeview ol > li.dragged.has-subitems > div:before { background-position: 0px -52px; } +.control-treeview ol > li.dragged div > ul.submenu { + display: none!important; +} .control-treeview ol > li > ol { padding-left: 20px; } @@ -13669,14 +13740,16 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { } .control-treeview ol > li.drop-target > div { background-color: #2581b8!important; +} +.control-treeview ol > li.drop-target > div > a { + color: #ffffff; +} +.control-treeview ol > li.drop-target > div > a > span.comment { color: #ffffff; } .control-treeview ol > li.drop-target > div:before { background-position: 0px -80px; } -.control-treeview ol > li.drop-target > div > span.comment { - color: #ffffff; -} .control-treeview ol > li.drop-target.has-subitems > div:before { background-position: 0px -52px; } @@ -13726,6 +13799,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -20px; padding-left: 71px; } +.control-treeview ol > li > ol > li > div > a { + margin-left: -71px; + padding-left: 71px; +} .control-treeview ol > li > ol > li > div:before { margin-left: 10px; } @@ -13736,6 +13813,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -40px; padding-left: 81px; } +.control-treeview ol > li > ol > li > ol > li > div > a { + margin-left: -81px; + padding-left: 81px; +} .control-treeview ol > li > ol > li > ol > li > div:before { margin-left: 20px; } @@ -13746,6 +13827,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -60px; padding-left: 91px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -91px; + padding-left: 91px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 30px; } @@ -13756,6 +13841,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -80px; padding-left: 101px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -101px; + padding-left: 101px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 40px; } @@ -13766,6 +13855,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -100px; padding-left: 111px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -111px; + padding-left: 111px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 50px; } @@ -13776,6 +13869,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -120px; padding-left: 121px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -121px; + padding-left: 121px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 60px; } @@ -13786,6 +13883,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -140px; padding-left: 131px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -131px; + padding-left: 131px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 70px; } @@ -13796,6 +13897,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -160px; padding-left: 141px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -141px; + padding-left: 141px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 80px; } @@ -13806,6 +13911,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -180px; padding-left: 151px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -151px; + padding-left: 151px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 90px; } @@ -13816,6 +13925,10 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover { margin-left: -200px; padding-left: 161px; } +.control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div > a { + margin-left: -161px; + padding-left: 161px; +} .control-treeview ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > ol > li > div:before { margin-left: 100px; } diff --git a/modules/backend/assets/images/treeview-submenu-tabs.png b/modules/backend/assets/images/treeview-submenu-tabs.png new file mode 100644 index 000000000..198f46cb4 Binary files /dev/null and b/modules/backend/assets/images/treeview-submenu-tabs.png differ diff --git a/modules/backend/assets/js/october.inputpreset.js b/modules/backend/assets/js/october.inputpreset.js index e392465ed..21b04926b 100644 --- a/modules/backend/assets/js/october.inputpreset.js +++ b/modules/backend/assets/js/october.inputpreset.js @@ -122,33 +122,6 @@ "than", "the", "this", "that", "to", "up", "via", "with" ] - var InputPreset = function (element, options) { - var $el = this.$el = $(element); - this.options = options || {}; - this.cancelled = false; - - // Do not update the element if it already has a value - if ($el.val().length) - return - - var parent = options.inputPresetClosestParent !== undefined ? - $el.closest(options.inputPresetClosestParent) : - undefined, - self = this; - - this.$src = $(options.inputPreset, parent), - this.$src.on('keyup', function() { - if (self.cancelled) - return; - - $el.val(self.formatValue()) - }) - - this.$el.on('change', function() { - self.cancelled = true - }) - } - var Downcoder = { Initialize: function() { if (Downcoder.map) { @@ -214,21 +187,30 @@ this.options = options || {} this.cancelled = false - // Do not update the element if it already has a value - if ($el.val().length) - return - var parent = options.inputPresetClosestParent !== undefined ? $el.closest(options.inputPresetClosestParent) : undefined, - self = this + self = this, + prefix = '' + + if (options.inputPresetPrefixInput !== undefined) + prefix = $(options.inputPresetPrefixInput, parent).val() + + if (prefix === undefined) + prefix = '' + + // Do not update the element if it already has a value and the value doesn't match the prefix + if ($el.val().length && $el.val() != prefix) + return + + $el.val(prefix) this.$src = $(options.inputPreset, parent), this.$src.on('keyup', function() { if (self.cancelled) return - $el.val(self.formatValue()) + $el.val(prefix + self.formatValue()) }) this.$el.on('change', function() { @@ -252,7 +234,8 @@ InputPreset.DEFAULTS = { inputPreset: '', inputPresetType: 'file', - inputPresetClosestParent: undefined + inputPresetClosestParent: undefined, + inputPresetPrefixInput: undefined } // INPUT CONVERTER PLUGIN DEFINITION diff --git a/modules/backend/assets/js/october.treelist.js b/modules/backend/assets/js/october.treelist.js index 0c6e1907d..6c48263c2 100644 --- a/modules/backend/assets/js/october.treelist.js +++ b/modules/backend/assets/js/october.treelist.js @@ -33,6 +33,11 @@ $el.find('> ol').sortable($.extend(sortableOptions, options)) } + TreeListWidget.prototype.unbind = function() { + this.$el.find('> ol').sortable('destroy') + this.$el.removeData('oc.treelist') + } + TreeListWidget.DEFAULTS = { handle: null } @@ -51,7 +56,7 @@ var data = $this.data('oc.treelist') var options = $.extend({}, TreeListWidget.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data) $this.data('oc.treelist', (data = new TreeListWidget(this, options))) - if (typeof option == 'string') result = data[option].call($this) + if (typeof option == 'string') result = data[option].call(data) if (typeof result != 'undefined') return false }) diff --git a/modules/backend/assets/js/october.treeview.js b/modules/backend/assets/js/october.treeview.js index a85421a9c..0e6e5a3a2 100644 --- a/modules/backend/assets/js/october.treeview.js +++ b/modules/backend/assets/js/october.treeview.js @@ -30,36 +30,7 @@ * Init the sortable */ - this.$el.treeListWidget({ - tweakCursorAdjustment: function(adjustment) { - if (!adjustment) - return adjustment - - if (self.$scrollbar.length > 0) { - adjustment.top -= self.$scrollbar.scrollTop() - } - - return adjustment; - }, - isValidTarget: function($item, container) { - return $(container.el).closest('li').attr('data-status') != 'collapsed' - }, - useAnimation: true, - handle: 'span.drag-handle' - }) - - this.$el.on('move.oc.treelist', function(){ - setTimeout(function(){ - self.$allItems.removeClass('drop-target') - self.fixSubItems() - self.sendReorderRequest() - }, 50) - }) - - this.$el.on('aftermove.oc.treelist', function(event, data) { - self.$allItems.removeClass('drop-target') - data.container.el.closest('li').addClass('drop-target') - }) + this.initSortable() /* * Create expand/collapse icons and drag handles @@ -71,8 +42,15 @@ * Bind the click events */ - this.$el.on('click', 'li > div', function(event) { - var e = $.Event('open.oc.treeview', {relatedTarget: $(this).parent().get(0), clickEvent: event}) + this.$el.on('click', 'li > div > ul.submenu li a', function(event) { + var e = $.Event('submenu.oc.treeview', {relatedTarget: this, clickEvent: event}) + self.$el.trigger(e, this) + + return false + }) + + this.$el.on('click', 'li > div > a', function(event) { + var e = $.Event('open.oc.treeview', {relatedTarget: $(this).closest('li').get(0), clickEvent: event}) self.$el.trigger(e, this) return false @@ -192,6 +170,44 @@ this.$el.request(this.options.reorderHandler, {data: {structure: JSON.stringify(groups)}}) } + TreeView.prototype.initSortable = function() { + var self = this + + if (this.$el.data('oc.treelist')) + this.$el.treeListWidget('unbind') + + this.$el.treeListWidget({ + tweakCursorAdjustment: function(adjustment) { + if (!adjustment) + return adjustment + + if (self.$scrollbar.length > 0) { + adjustment.top -= self.$scrollbar.scrollTop() + } + + return adjustment; + }, + isValidTarget: function($item, container) { + return $(container.el).closest('li').attr('data-status') != 'collapsed' + }, + useAnimation: true, + handle: 'span.drag-handle' + }) + + this.$el.on('move.oc.treelist', function(){ + setTimeout(function(){ + self.$allItems.removeClass('drop-target') + self.fixSubItems() + self.sendReorderRequest() + }, 50) + }) + + this.$el.on('aftermove.oc.treelist', function(event, data) { + self.$allItems.removeClass('drop-target') + data.container.el.closest('li').addClass('drop-target') + }) + } + TreeView.prototype.markActive = function(dataId) { $('li', this.$el).removeClass('active') @@ -202,7 +218,9 @@ } TreeView.prototype.update = function() { + this.$allItems = $('li', this.$el) this.createItemControls() + this.initSortable() if (this.dataId !== undefined) this.markActive(this.dataId) diff --git a/modules/backend/assets/less/controls/treeview.less b/modules/backend/assets/less/controls/treeview.less index e97c8e6d6..35e14b090 100644 --- a/modules/backend/assets/less/controls/treeview.less +++ b/modules/backend/assets/less/controls/treeview.less @@ -1,4 +1,6 @@ .control-treeview { + margin-bottom: 40px; + ol { margin: 0; padding: 0; @@ -11,12 +13,17 @@ > div { font-size: 13px; font-weight: 600; - color: @color-treeview-item-title; background: @color-treeview-item-bg; border-bottom: 1px solid @color-panel-light; - padding: 16px 16px 15px 61px; position: relative; - cursor: pointer; + + > a { + color: @color-treeview-item-title; + padding: 16px 45px 15px 61px; + display: block; + text-decoration: none; + .box-sizing(border-box); + } &:before { content: ' '; @@ -71,7 +78,7 @@ .transition(opacity 0.4s); position: absolute; - right: 10px; + right: 24px; bottom: 5px; width: 14px; height: 14px; @@ -88,11 +95,83 @@ span.borders { font-size: 0; } + + > ul.submenu { + position: absolute; + left: 20px; + bottom: -37px; + padding: 0; + list-style: none; + z-index: 200; + height: 37px; + display: none; + margin-left: 15px; + + background: transparent url(../images/treeview-submenu-tabs.png) repeat-x left -39px; + + &:before, &:after { + background: transparent url(../images/treeview-submenu-tabs.png) no-repeat left top; + content: ' '; + display: block; + width: 20px; + height: 37px; + position: absolute; + top: 0; + } + + &:before { + left: -20px; + } + + &:after { + background-position: -100px top; + right: -20px; + } + + li { + font-size: 12px; + + a { + display: block; + padding: 4px 3px 0 3px; + color: @color-treeview-submenu-text; + text-decoration: none; + outline: none; + + i { + margin-right: 5px; + } + } + } + } + + &:hover { + > ul.submenu { + display: block; + } + } + + .checkbox { + position: absolute; + top: 19px; + right: 17px; + + label { + margin-right: 0; + + &:before { + border-color: @color-filelist-cb-border; + } + } + } } &.dragged div, > div:hover { background-color: @color-treeview-hover-bg!important; - color: @color-treeview-hover-text!important; + + > a { + color: @color-treeview-hover-text!important; + } &:before { background-position: 0px -80px; @@ -123,6 +202,10 @@ background-position: 0px -52px; } } + + div > ul.submenu { + display: none!important; + } } > ol { @@ -193,15 +276,17 @@ &.drop-target { > div { background-color: #2581b8!important; - color: @color-treeview-hover-text; + + > a { + color: @color-treeview-hover-text; + > span.comment { + color: @color-treeview-hover-text; + } + } &:before { background-position: 0px -80px; } - - > span.comment { - color: @color-treeview-hover-text; - } } &.has-subitems > div:before { @@ -264,6 +349,11 @@ margin-left: -20-(@max-level - @level)*20px; padding-left: 61+(@max-level - @level + 1)*10px; + > a { + margin-left: -61-(@max-level - @level + 1)*10px; + padding-left: 61+(@max-level - @level + 1)*10px; + } + &:before { margin-left: (@max-level - @level + 1)*10px; } diff --git a/modules/backend/assets/less/core/variables.less b/modules/backend/assets/less/core/variables.less index 9162120a0..63b648d53 100644 --- a/modules/backend/assets/less/core/variables.less +++ b/modules/backend/assets/less/core/variables.less @@ -270,6 +270,7 @@ @color-treeview-hover-bg: #58b6f7; @color-treeview-hover-text: #ffffff; @color-treeview-item-active-comment: #8f8f8f; +@color-treeview-submenu-text: #ffffff; // // Sizes diff --git a/modules/backend/formwidgets/richeditor/assets/js/richeditor.js b/modules/backend/formwidgets/richeditor/assets/js/richeditor.js index 10eb2c037..9fecb45ee 100644 --- a/modules/backend/formwidgets/richeditor/assets/js/richeditor.js +++ b/modules/backend/formwidgets/richeditor/assets/js/richeditor.js @@ -19,6 +19,7 @@ this.options = options this.$el = $(element) this.$textarea = this.$el.find('>textarea:first') + this.$form = this.$el.closest('form') this.editor = null this.init(); @@ -45,7 +46,8 @@ var redactorOptions = { focusCallback: function() { self.$el.addClass('editor-focus') }, blurCallback: function() { self.$el.removeClass('editor-focus') }, - initCallback: function() { self.build() } + initCallback: function() { self.build() }, + changeCallback: function() { self.onChange() } } if (this.options.stylesheet) { @@ -61,11 +63,11 @@ } RichEditor.prototype.build = function() { - var $iframe = this.$textarea.redactor('getIframe'), - $toolbar = this.$textarea.redactor('getToolbar'), + var $iframe = $('iframe', this.$el), + $toolbar = $('.redactor_toolbar', this.$el), $html = $('html') - if (!$iframe) + if (!$iframe.length) return if (this.$el.hasClass('stretch')) { @@ -84,6 +86,11 @@ }) } + RichEditor.prototype.onChange = function() { + this.$form.trigger('change') + + } + // RICHEDITOR PLUGIN DEFINITION // ============================ diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index 980040c44..7d2c50bf1 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -5,6 +5,7 @@ use Cms\Classes\CodeBase; use System\Classes\SystemException; use Cms\Classes\FileHelper; use October\Rain\Support\ValidationException; +use Cms\Classes\ViewBag; /** * This is a base class for CMS objects that have multiple sections - pages, partials and layouts. @@ -47,6 +48,10 @@ class CmsCompoundObject extends CmsObject protected $settingsValidationMessages = []; + protected $viewBagValidationRules = []; + + protected $viewBagValidationMessages = []; + protected $viewBagCache = false; /** @@ -217,8 +222,12 @@ class CmsCompoundObject extends CmsObject $componentName = 'viewBag'; - if (!isset($this->settings['components'][$componentName])) - return $this->viewBagCache = null; + if (!isset($this->settings['components'][$componentName])) { + $viewBag = new ViewBag(null, []); + $viewBag->name = $componentName; + + return $this->viewBagCache = $viewBag; + } return $this->viewBagCache = ComponentManager::instance()->makeComponent( $componentName, @@ -266,5 +275,11 @@ class CmsCompoundObject extends CmsObject $validation = Validator::make($this->settings, $this->settingsValidationRules, $this->settingsValidationMessages); if ($validation->fails()) throw new ValidationException($validation); + + if ($this->viewBagValidationRules && isset($this->settings['viewBag'])) { + $validation = Validator::make($this->settings['viewBag'], $this->viewBagValidationRules, $this->viewBagValidationMessages); + if ($validation->fails()) + throw new ValidationException($validation); + } } } \ No newline at end of file