diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index 4bc609f14..18a0bf078 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -7077,6 +7077,10 @@ body.display-side-panel #layout-side-panel { -webkit-box-shadow: 2px 0px 2px 0 rgba(0, 0, 0, 0.3); box-shadow: 2px 0px 2px 0 rgba(0, 0, 0, 0.3); } +body.side-panel-fix-shadow #layout-side-panel { + -webkit-box-shadow: none; + box-shadow: none; +} .touch #layout-side-panel .fix-button { display: none; } diff --git a/modules/backend/assets/js/october.layout.js b/modules/backend/assets/js/october.layout.js index 7c97e9191..f671dc6c1 100644 --- a/modules/backend/assets/js/october.layout.js +++ b/modules/backend/assets/js/october.layout.js @@ -1,5 +1,16 @@ (function($){ - function updateLayout() { + var OctoberLayout = function() {} + + OctoberLayout.prototype.setPageTitle = function(title) { + var $title = $('title') + + if (this.pageTitleTemplate === undefined) + this.pageTitleTemplate = $title.data('titleTemplate') + + $title.text(this.pageTitleTemplate.replace('%s', title)) + } + + OctoberLayout.prototype.updateLayout = function(title) { $('.layout-cell.width-fix').each(function(){ var $el = $(this).children(); if ($el.length > 0) { @@ -14,6 +25,13 @@ }) } - $(document).ready(updateLayout) - $(window).on('resize', updateLayout) + if ($.oc === undefined) + $.oc = {} + + $.oc.layout = new OctoberLayout() + + $(document).ready($.oc.layout.updateLayout()) + $(window).on('resize', function() { + $.oc.layout.updateLayout() + }) })(jQuery); \ No newline at end of file diff --git a/modules/backend/assets/js/october.sidepaneltab.js b/modules/backend/assets/js/october.sidepaneltab.js index 3eb1f08be..989956cb0 100644 --- a/modules/backend/assets/js/october.sidepaneltab.js +++ b/modules/backend/assets/js/october.sidepaneltab.js @@ -136,14 +136,22 @@ SidePanelTab.prototype.fixPanel = function() { $(document.body).toggleClass('side-panel-not-fixed') - var fixed = this.panelFixed() + var self = this - fixed - ? this.updateActiveTab() - : this.hideSidePanel() + window.setTimeout(function() { + var fixed = self.panelFixed() - if (typeof(localStorage) !== 'undefined') - localStorage.ocSidePanelFixed = fixed ? 1 : 0 + if (fixed) { + self.updateActiveTab() + $(document.body).addClass('side-panel-fix-shadow') + } else { + $(document.body).removeClass('side-panel-fix-shadow') + self.hideSidePanel() + } + + if (typeof(localStorage) !== 'undefined') + localStorage.ocSidePanelFixed = fixed ? 1 : 0 + }, 0) } SidePanelTab.DEFAULTS = { diff --git a/modules/backend/assets/js/october.tab.js b/modules/backend/assets/js/october.tab.js index 37761cc27..3def74610 100644 --- a/modules/backend/assets/js/october.tab.js +++ b/modules/backend/assets/js/october.tab.js @@ -131,7 +131,8 @@ var $tabs = $('>li', this.$tabsContainer), tabIndex = $tabs.index(li), - targetId = this.tabId + '-tab-' + tabIndex, + time = new Date().getTime(), + targetId = this.tabId + '-tab-' + tabIndex + time, $a = $('a', li) $a.attr('data-target', '#'+targetId).attr('data-toggle', 'tab') diff --git a/modules/backend/assets/less/layout/sidepanel.less b/modules/backend/assets/less/layout/sidepanel.less index 3ce2a7f70..31fdd9c1d 100644 --- a/modules/backend/assets/less/layout/sidepanel.less +++ b/modules/backend/assets/less/layout/sidepanel.less @@ -42,7 +42,13 @@ body.display-side-panel { position: absolute; z-index: 500; width: 300px; - .box-shadow(2px 0px 2px 0 rgba(0, 0, 0, 0.3)) + .box-shadow(2px 0px 2px 0 rgba(0, 0, 0, 0.3)); + } +} + +body.side-panel-fix-shadow { + #layout-side-panel { + .box-shadow(none); } } diff --git a/modules/backend/classes/Controller.php b/modules/backend/classes/Controller.php index 6bc7e434a..4594c3d07 100644 --- a/modules/backend/classes/Controller.php +++ b/modules/backend/classes/Controller.php @@ -76,6 +76,11 @@ class Controller extends Extendable */ public $pageTitle; + /** + * @var string Page title template + */ + public $pageTitleTemplate; + /** * @var string Body class property used for customising the layout on a controller basis. */ diff --git a/modules/backend/layouts/_head.htm b/modules/backend/layouts/_head.htm index b8eb382d3..487c74f20 100644 --- a/modules/backend/layouts/_head.htm +++ b/modules/backend/layouts/_head.htm @@ -1,6 +1,8 @@ -<?= $this->pageTitle ?> | October CMS + + <?= $this->pageTitle ?> | October CMS + diff --git a/modules/cms/assets/css/october.components.css b/modules/cms/assets/css/october.components.css index 121845084..b4994db8c 100644 --- a/modules/cms/assets/css/october.components.css +++ b/modules/cms/assets/css/october.components.css @@ -118,6 +118,15 @@ div.control-componentlist.has-components { div.control-componentlist div.layout { width: auto; } +div.control-componentlist div.components div.layout-cell.error-component { + background: #ab2a1c; +} +div.control-componentlist div.components div.layout-cell.error-component > div { + color: #ffffff; +} +div.control-componentlist div.components div.layout-cell.error-component > div:after { + color: #ab2a1c; +} div.control-componentlist div.components div.layout-cell:first-child { border-bottom-left-radius: 3px; border-top-left-radius: 3px; diff --git a/modules/cms/assets/js/october.cmspage.js b/modules/cms/assets/js/october.cmspage.js index 2a8fdc04d..bf14df02b 100644 --- a/modules/cms/assets/js/october.cmspage.js +++ b/modules/cms/assets/js/october.cmspage.js @@ -40,7 +40,7 @@ success: function(data) { this.success(data).done(function(){ $.oc.stripeLoadIndicator.hide() - $('#cms-master-tabs').ocTab('addTab', data.title, data.tab, tabId, $form.data('type-icon')) + $('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon')) }).always(function(){ $.oc.stripeLoadIndicator.hide() }) @@ -82,6 +82,21 @@ */ $('#cms-master-tabs').on('closed.oc.tab', function(event){ updateModifiedCounter() + + if ($('> div.tab-content > div.tab-pane', '#cms-master-tabs').length == 0) + setPageTitle('') + }) + + /* + * Listen for the onBeforeRequest event + */ + $('#cms-master-tabs').on('oc.beforeRequest', function(event) { + var $form = $(event.target) + + if ( $('.components .layout-cell.error-component', $form).length > 0) { + if (!confirm('The form contains unknown components. Their properties will be lost on save. Do you want to save the form?')) + event.preventDefault() + } }) /* @@ -92,6 +107,11 @@ return var dataId = $(event.target).closest('li').attr('data-tab-id') + + var title = $(event.target).attr('title') + if (title) + setPageTitle(title) + $('#cms-side-panel [data-control=filelist]').fileList('markActive', dataId) $('#cms-side-panel form').trigger('oc.list.setActiveItem', [dataId]) }) @@ -202,8 +222,10 @@ $('[data-control=preview-button]', this).attr('href', data.pageUrl) } - if (data.title !== undefined) - $('#cms-master-tabs').ocTab('updateTitle', $(this).closest('.tab-pane'), data.title) + if (data.tabTitle !== undefined) { + $('#cms-master-tabs').ocTab('updateTitle', $(this).closest('.tab-pane'), data.tabTitle) + setPageTitle(data.tabTitle) + } var tabId = $('input[name=templateType]', this).val() + '-' + $('input[name=theme]', this).val() + '-' @@ -251,8 +273,9 @@ }, success: function(data) { this.success(data).done(function(){ - $('#cms-master-tabs').ocTab('addTab', data.title, data.tab, tabId, $form.data('type-icon') + ' new-template') + $('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon') + ' new-template') $('#layout-side-panel').trigger('close.oc.sidePanel') + setPageTitle(data.tabTitle) }).always(function(){ $.oc.stripeLoadIndicator.hide() }) @@ -446,7 +469,7 @@ success: function(data) { this.success(data).done(function(){ $.oc.stripeLoadIndicator.hide() - $('#cms-master-tabs').ocTab('updateTab', tab, data.title, data.tab) + $('#cms-master-tabs').ocTab('updateTab', tab, data.tabTitle, data.tab) $('#cms-master-tabs').ocTab('unmodifyTab', tab) updateModifiedCounter() }).always(function(){ @@ -460,6 +483,13 @@ }) } + function setPageTitle(title) { + if (title.length) + $.oc.layout.setPageTitle(title + ' | ') + else + $.oc.layout.setPageTitle(title) + } + /* * Listen for the click event on the components' remove link */ diff --git a/modules/cms/assets/less/october.components.less b/modules/cms/assets/less/october.components.less index 3c8c888ca..40589f41d 100644 --- a/modules/cms/assets/less/october.components.less +++ b/modules/cms/assets/less/october.components.less @@ -11,6 +11,8 @@ @color-component-hover-text: #ffffff; @color-component-placeholder: #e0e0e0; @color-group-bg: #f1f3f4; +@color-error-component-bg: #ab2a1c; +@color-error-component-text: #ffffff; .component-lego-icon() { position: absolute; @@ -117,6 +119,18 @@ div.control-componentlist { div.components { div.layout-cell { + &.error-component { + background: @color-error-component-bg; + + > div { + color: @color-error-component-text; + + &:after { + color: @color-error-component-bg; + } + } + } + &:first-child { .border-left-radius(3px); } diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index e5af60ff5..c37c46d7f 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -133,8 +133,8 @@ class CmsCompoundObject extends CmsObject $settingParts = explode(' ', $setting); $settingName = $settingParts[0]; - if (!$manager->hasComponent($settingName)) - continue; + // if (!$manager->hasComponent($settingName)) + // continue; $components[$setting] = $value; unset($this->settings[$setting]); diff --git a/modules/cms/classes/ComponentBase.php b/modules/cms/classes/ComponentBase.php index 2a37180d6..7e6d38eed 100644 --- a/modules/cms/classes/ComponentBase.php +++ b/modules/cms/classes/ComponentBase.php @@ -39,6 +39,18 @@ abstract class ComponentBase extends Extendable */ public $pluginIcon; + /** + * @var string Component CSS class name for the back-end page/layout component list. + * This field is used by the CMS internally. + */ + public $componentCssClass; + + /** + * @var boolean Determines whether Inspector can be used with the component. + * This field is used by the CMS internally. + */ + public $inspectorEnabled = true; + /** * @var string Specifies the component directory name. */ diff --git a/modules/cms/classes/ComponentManager.php b/modules/cms/classes/ComponentManager.php index 76168cfd8..8674c7a33 100644 --- a/modules/cms/classes/ComponentManager.php +++ b/modules/cms/classes/ComponentManager.php @@ -3,6 +3,7 @@ use Str; use Illuminate\Container\Container; use System\Classes\PluginManager; +use System\Classes\SystemException; /** * Component manager @@ -179,10 +180,10 @@ class ComponentManager { $className = $this->resolve($name); if (!$className) - return null; + throw new SystemException(sprintf('Class name is not registered for the component %s. Check the component plugin.', $name)); if (!class_exists($className)) - throw new \Exception('Component class not found '.$className); + throw new SystemException(sprintf('Component class not found %s.Check the component plugin.', $className)); $component = new $className($cmsObject, $properties); $component->name = $name; diff --git a/modules/cms/classes/UnknownComponent.php b/modules/cms/classes/UnknownComponent.php new file mode 100644 index 000000000..5d9c9c5ed --- /dev/null +++ b/modules/cms/classes/UnknownComponent.php @@ -0,0 +1,29 @@ +errorMessage = $errorMessage; + $this->componentCssClass = 'error-component'; + $this->inspectorEnabled = false; + + parent::__construct($cmsObject, $properties); + } + + public function componentDetails() + { + return [ + 'name' => 'Uknown component', + 'description' => $this->errorMessage + ]; + } +} \ No newline at end of file diff --git a/modules/cms/controllers/Index.php b/modules/cms/controllers/Index.php index 91e895dfd..e14845c5d 100644 --- a/modules/cms/controllers/Index.php +++ b/modules/cms/controllers/Index.php @@ -90,6 +90,7 @@ class Index extends Controller $this->bodyClass = 'compact-container side-panel-not-fixed'; $this->pageTitle = Lang::get('cms::lang.cms.menu_label'); + $this->pageTitleTemplate = '%s CMS | October'; } // @@ -116,7 +117,7 @@ class Index extends Controller } return [ - 'title' => $this->getTabTitle($type, $template), + 'tabTitle' => $this->getTabTitle($type, $template), 'tab' => $this->makePartial('form_page', [ 'form' => $widget, 'templateType' => $type, @@ -162,7 +163,7 @@ class Index extends Controller $result = [ 'templatePath' => $template->fileName, 'templateMtime' => $template->mtime, - 'title' => $this->getTabTitle($type, $template) + 'tabTitle' => $this->getTabTitle($type, $template) ]; if ($type == 'page') { @@ -192,7 +193,7 @@ class Index extends Controller $this->vars['templatePath'] = ''; return [ - 'title' => $this->getTabTitle($type, $template), + 'tabTitle' => $this->getTabTitle($type, $template), 'tab' => $this->makePartial('form_page', [ 'form' => $widget, 'templateType' => $type, diff --git a/modules/cms/formwidgets/Components.php b/modules/cms/formwidgets/Components.php index 483831191..b52df6785 100644 --- a/modules/cms/formwidgets/Components.php +++ b/modules/cms/formwidgets/Components.php @@ -3,7 +3,9 @@ use Backend\Classes\FormWidgetBase; use Cms\Classes\ComponentManager; use Cms\Classes\ComponentHelpers; +use Cms\Classes\UnknownComponent; use Lang; +use Exception; /** * Component Builder @@ -36,15 +38,22 @@ class Components extends FormWidgetBase foreach ($this->model->settings['components'] as $name=>$properties) { list($name, $alias) = strpos($name, ' ') ? explode(' ', $name) : [$name, $name]; - $componentObj = $manager->makeComponent($name, null, $properties); - $componentObj->alias = $alias; - $componentObj->pluginIcon = 'icon-puzzle-piece'; + try { + $componentObj = $manager->makeComponent($name, null, $properties); - $plugin = $manager->findComponentPlugin($componentObj); - if ($plugin) { - $pluginDetails = $plugin->pluginDetails(); - if (isset($pluginDetails['icon'])) - $componentObj->pluginIcon = $pluginDetails['icon']; + $componentObj->alias = $alias; + $componentObj->pluginIcon = 'icon-puzzle-piece'; + + $plugin = $manager->findComponentPlugin($componentObj); + if ($plugin) { + $pluginDetails = $plugin->pluginDetails(); + if (isset($pluginDetails['icon'])) + $componentObj->pluginIcon = $pluginDetails['icon']; + } + } catch (Exception $ex) { + $componentObj = new UnknownComponent(null, $properties, $ex->getMessage()); + $componentObj->alias = $alias; + $componentObj->pluginIcon = 'icon-bug'; } $result[] = $componentObj; diff --git a/modules/cms/formwidgets/components/partials/_formcomponents.htm b/modules/cms/formwidgets/components/partials/_formcomponents.htm index e065303b1..0c769d9cd 100644 --- a/modules/cms/formwidgets/components/partials/_formcomponents.htm +++ b/modules/cms/formwidgets/components/partials/_formcomponents.htm @@ -2,15 +2,21 @@
-
-
- - - alias) ?> - - - - × +
+
inspectorEnabled): ?>data-inspectable + data-inspector-title="getComponentName($component)) ?>" + data-inspector-description="getComponentDescription($component)) ?>" + data-inspector-config="getComponentsPropertyConfig($component)) ?>" + data-inspector-class=""> + + + alias) ?> + + + + ×
diff --git a/modules/system/assets/js/framework.js b/modules/system/assets/js/framework.js index b9c28a623..3b5e0c4e0 100644 --- a/modules/system/assets/js/framework.js +++ b/modules/system/assets/js/framework.js @@ -45,7 +45,9 @@ if (window.jQuery === undefined) loading = options.loading !== undefined && options.loading.length ? $(options.loading) : null, isRedirect = options.redirect !== undefined && options.redirect.length - form.trigger('oc.beforeRequest', context) + var _event = jQuery.Event('oc.beforeRequest') + form.trigger(_event, context) + if (_event.isDefaultPrevented()) return var data = [form.serialize()]