From 8d4066f37d203265756f0060bad6561b21549862 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 3 Sep 2026 20:34:48 +1000 Subject: [PATCH 01/11] Rewrite to use Editor module --- Plugin.php | 112 +- assets/css/editor.css | 67 ++ assets/css/pages.css | 1 - assets/css/treeview.css | 1 - assets/images/content-icons.png | Bin 1761 -> 0 bytes assets/images/loader-transparent.svg | 20 - assets/images/menu-icons.png | Bin 1198 -> 0 bytes assets/images/snippet-icons.png | Bin 1436 -> 0 bytes assets/images/treeview-icons.png | Bin 1344 -> 0 bytes assets/images/treeview-submenu-tabs.png | Bin 2714 -> 0 bytes assets/js/october.treeview.js | 449 -------- assets/js/pages-page.js | 713 ------------- ...or.extension.documentcontroller.content.js | 20 + ...ditor.extension.documentcontroller.menu.js | 20 + ...extension.documentcontroller.staticpage.js | 109 ++ assets/js/pages.editor.extension.js | 32 + assets/less/pages.less | 209 ---- assets/less/treeview.less | 634 ------------ behaviors/EditorState.php | 22 + classes/Content.php | 20 +- classes/Controller.php | 25 +- classes/EditorExtension.php | 446 ++++++++ classes/Menu.php | 38 +- classes/MenuItem.php | 53 +- classes/MenuItemReference.php | 22 +- classes/Page.php | 141 +-- classes/PageList.php | 34 +- classes/Router.php | 29 +- classes/content/fields.yaml | 38 - classes/editorextension/HasContentCrud.php | 179 ++++ classes/editorextension/HasMenuCrud.php | 219 ++++ classes/editorextension/HasStaticPageCrud.php | 323 ++++++ classes/menu/Fields.php | 37 + classes/menu/fields.yaml | 34 - classes/menuitem/fields.yaml | 46 +- classes/page/fields.yaml | 67 -- classes/staticpage/Fields.php | 71 ++ components/ChildPages.php | 27 +- components/StaticBreadcrumbs.php | 19 +- components/StaticMenu.php | 43 +- components/StaticPage.php | 59 +- composer.json | 11 +- config/config.php | 27 - controllers/Index.php | 973 ++---------------- controllers/index/HasMenuItemForm.php | 76 ++ controllers/index/HasSyntaxFields.php | 133 +++ .../index/_concurrency_resolve_form.htm | 29 - controllers/index/_content_toolbar.htm | 51 - controllers/index/_form_page.htm | 25 - controllers/index/_menu_toolbar.htm | 51 - controllers/index/_page_toolbar.htm | 61 -- controllers/index/_sidepanel.htm | 29 - controllers/index/config_content_list.yaml | 11 - controllers/index/index.htm | 32 - controllers/index/index.php | 12 + docs/menu-item.png | Bin 41213 -> 0 bytes docs/menu-management.png | Bin 23538 -> 0 bytes docs/snippets-backend.png | Bin 32717 -> 0 bytes docs/snippets-partial.png | Bin 45280 -> 0 bytes docs/static-layout.png | Bin 31822 -> 0 bytes docs/static-page.png | Bin 104609 -> 0 bytes formwidgets/Components.php | 92 -- formwidgets/MenuItemSearch.php | 11 +- formwidgets/MenuItems.php | 191 ---- formwidgets/MenuPicker.php | 49 - formwidgets/PagePicker.php | 64 -- .../components/partials/_component.htm | 17 - .../components/partials/_formcomponents.htm | 17 - .../menuitems/assets/js/menu-items-editor.js | 651 ------------ .../menuitems/partials/_editortemplate.htm | 29 - formwidgets/menuitems/partials/_item.htm | 38 - formwidgets/menuitems/partials/_itemlist.htm | 5 - formwidgets/menuitems/partials/_items.htm | 11 - formwidgets/menuitems/partials/_menuitems.htm | 36 - lang/cs/lang.php | 105 -- lang/de/lang.php | 93 -- lang/el/lang.php | 113 -- lang/en.json | 75 ++ lang/en/lang.php | 130 --- lang/es/lang.php | 94 -- lang/fa/lang.php | 113 -- lang/fi/lang.php | 151 --- lang/fr/lang.php | 123 --- lang/hu/lang.php | 125 --- lang/it/lang.php | 96 -- lang/lv/lang.php | 101 -- lang/nb-no/lang.php | 93 -- lang/nl/lang.php | 96 -- lang/pl/lang.php | 124 --- lang/pt-br/lang.php | 94 -- lang/ru/lang.php | 100 -- lang/sk/lang.php | 121 --- lang/sl/lang.php | 125 --- lang/sv/lang.php | 94 -- lang/tr/lang.php | 121 --- lang/uk/lang.php | 100 -- lang/zh-cn/lang.php | 113 -- rainlab-pages.mix.js | 15 - updates/version.yaml | 92 +- vuecomponents/ContentEditor.php | 21 + vuecomponents/MenuEditor.php | 12 + vuecomponents/StaticPageEditor.php | 20 + .../contenteditor/assets/js/contenteditor.js | 143 +++ .../contenteditor/partials/_contenteditor.php | 70 ++ .../menueditor/assets/js/menueditor.js | 518 ++++++++++ .../menueditor/partials/_menueditor.php | 92 ++ .../assets/js/staticpageeditor.js | 447 ++++++++ .../partials/_staticpageeditor.php | 93 ++ widgets/MenuList.php | 127 --- widgets/PageList.php | 165 --- widgets/TemplateList.php | 415 -------- widgets/menulist/partials/_body.htm | 10 - widgets/menulist/partials/_items.htm | 39 - widgets/menulist/partials/_menus.htm | 10 - widgets/menulist/partials/_toolbar.htm | 37 - widgets/pagelist/partials/_body.htm | 10 - widgets/pagelist/partials/_items.htm | 9 - widgets/pagelist/partials/_pages.htm | 12 - widgets/pagelist/partials/_toolbar.htm | 37 - widgets/pagelist/partials/_treebranch.htm | 69 -- widgets/templatelist/partials/_body.htm | 8 - widgets/templatelist/partials/_items.htm | 59 -- .../partials/_sorting-options.htm | 7 - widgets/templatelist/partials/_templates.htm | 11 - widgets/templatelist/partials/_toolbar.htm | 51 - 125 files changed, 3655 insertions(+), 8555 deletions(-) create mode 100644 assets/css/editor.css delete mode 100644 assets/css/pages.css delete mode 100644 assets/css/treeview.css delete mode 100644 assets/images/content-icons.png delete mode 100644 assets/images/loader-transparent.svg delete mode 100644 assets/images/menu-icons.png delete mode 100644 assets/images/snippet-icons.png delete mode 100644 assets/images/treeview-icons.png delete mode 100644 assets/images/treeview-submenu-tabs.png delete mode 100644 assets/js/october.treeview.js delete mode 100644 assets/js/pages-page.js create mode 100644 assets/js/pages.editor.extension.documentcontroller.content.js create mode 100644 assets/js/pages.editor.extension.documentcontroller.menu.js create mode 100644 assets/js/pages.editor.extension.documentcontroller.staticpage.js create mode 100644 assets/js/pages.editor.extension.js delete mode 100644 assets/less/pages.less delete mode 100644 assets/less/treeview.less create mode 100644 behaviors/EditorState.php create mode 100644 classes/EditorExtension.php delete mode 100644 classes/content/fields.yaml create mode 100644 classes/editorextension/HasContentCrud.php create mode 100644 classes/editorextension/HasMenuCrud.php create mode 100644 classes/editorextension/HasStaticPageCrud.php create mode 100644 classes/menu/Fields.php delete mode 100644 classes/menu/fields.yaml delete mode 100644 classes/page/fields.yaml create mode 100644 classes/staticpage/Fields.php delete mode 100644 config/config.php create mode 100644 controllers/index/HasMenuItemForm.php create mode 100644 controllers/index/HasSyntaxFields.php delete mode 100644 controllers/index/_concurrency_resolve_form.htm delete mode 100644 controllers/index/_content_toolbar.htm delete mode 100644 controllers/index/_form_page.htm delete mode 100644 controllers/index/_menu_toolbar.htm delete mode 100644 controllers/index/_page_toolbar.htm delete mode 100644 controllers/index/_sidepanel.htm delete mode 100644 controllers/index/config_content_list.yaml delete mode 100644 controllers/index/index.htm create mode 100644 controllers/index/index.php delete mode 100644 docs/menu-item.png delete mode 100644 docs/menu-management.png delete mode 100644 docs/snippets-backend.png delete mode 100644 docs/snippets-partial.png delete mode 100644 docs/static-layout.png delete mode 100644 docs/static-page.png delete mode 100644 formwidgets/Components.php delete mode 100644 formwidgets/MenuItems.php delete mode 100644 formwidgets/MenuPicker.php delete mode 100644 formwidgets/PagePicker.php delete mode 100644 formwidgets/components/partials/_component.htm delete mode 100644 formwidgets/components/partials/_formcomponents.htm delete mode 100644 formwidgets/menuitems/assets/js/menu-items-editor.js delete mode 100644 formwidgets/menuitems/partials/_editortemplate.htm delete mode 100644 formwidgets/menuitems/partials/_item.htm delete mode 100644 formwidgets/menuitems/partials/_itemlist.htm delete mode 100644 formwidgets/menuitems/partials/_items.htm delete mode 100644 formwidgets/menuitems/partials/_menuitems.htm delete mode 100644 lang/cs/lang.php delete mode 100644 lang/de/lang.php delete mode 100644 lang/el/lang.php create mode 100644 lang/en.json delete mode 100644 lang/en/lang.php delete mode 100644 lang/es/lang.php delete mode 100644 lang/fa/lang.php delete mode 100644 lang/fi/lang.php delete mode 100644 lang/fr/lang.php delete mode 100644 lang/hu/lang.php delete mode 100644 lang/it/lang.php delete mode 100644 lang/lv/lang.php delete mode 100644 lang/nb-no/lang.php delete mode 100644 lang/nl/lang.php delete mode 100644 lang/pl/lang.php delete mode 100644 lang/pt-br/lang.php delete mode 100644 lang/ru/lang.php delete mode 100644 lang/sk/lang.php delete mode 100644 lang/sl/lang.php delete mode 100644 lang/sv/lang.php delete mode 100644 lang/tr/lang.php delete mode 100644 lang/uk/lang.php delete mode 100644 lang/zh-cn/lang.php delete mode 100644 rainlab-pages.mix.js create mode 100644 vuecomponents/ContentEditor.php create mode 100644 vuecomponents/MenuEditor.php create mode 100644 vuecomponents/StaticPageEditor.php create mode 100644 vuecomponents/contenteditor/assets/js/contenteditor.js create mode 100644 vuecomponents/contenteditor/partials/_contenteditor.php create mode 100644 vuecomponents/menueditor/assets/js/menueditor.js create mode 100644 vuecomponents/menueditor/partials/_menueditor.php create mode 100644 vuecomponents/staticpageeditor/assets/js/staticpageeditor.js create mode 100644 vuecomponents/staticpageeditor/partials/_staticpageeditor.php delete mode 100644 widgets/MenuList.php delete mode 100644 widgets/PageList.php delete mode 100644 widgets/TemplateList.php delete mode 100644 widgets/menulist/partials/_body.htm delete mode 100644 widgets/menulist/partials/_items.htm delete mode 100644 widgets/menulist/partials/_menus.htm delete mode 100644 widgets/menulist/partials/_toolbar.htm delete mode 100644 widgets/pagelist/partials/_body.htm delete mode 100644 widgets/pagelist/partials/_items.htm delete mode 100644 widgets/pagelist/partials/_pages.htm delete mode 100644 widgets/pagelist/partials/_toolbar.htm delete mode 100644 widgets/pagelist/partials/_treebranch.htm delete mode 100644 widgets/templatelist/partials/_body.htm delete mode 100644 widgets/templatelist/partials/_items.htm delete mode 100644 widgets/templatelist/partials/_sorting-options.htm delete mode 100644 widgets/templatelist/partials/_templates.htm delete mode 100644 widgets/templatelist/partials/_toolbar.htm diff --git a/Plugin.php b/Plugin.php index 0d68b943..7208e66d 100644 --- a/Plugin.php +++ b/Plugin.php @@ -9,19 +9,41 @@ use Cms\Classes\Controller as CmsController; use System\Classes\PluginBase; +/** + * Plugin for the modernized Pages editor, rebuilt on the Vue Editor module. + * + * The file-based data model and frontend components are preserved from the original for + * drop-in compatibility; only the backend editing experience is modernized. + */ class Plugin extends PluginBase { + /** + * register the Editor extension for the backend Pages editor. + */ + public function register() + { + Event::listen('editor.extension.register', function () { + return \RainLab\Pages\Classes\EditorExtension::class; + }); + } + + /** + * pluginDetails returns information about this plugin. + */ public function pluginDetails() { return [ - 'name' => 'rainlab.pages::lang.plugin.name', - 'description' => 'rainlab.pages::lang.plugin.description', + 'name' => 'Pages', + 'description' => 'Pages & menus features.', 'author' => 'Alexey Bobkov, Samuel Georges', 'icon' => 'icon-files-o', 'homepage' => 'https://github.com/rainlab/pages-plugin' ]; } + /** + * registerComponents used by the frontend, preserved for theme compatibility. + */ public function registerComponents() { return [ @@ -32,74 +54,63 @@ public function registerComponents() ]; } + /** + * registerPermissions available for backend users. + */ public function registerPermissions() { return [ 'rainlab.pages.manage_pages' => [ - 'tab' => 'rainlab.pages::lang.page.tab', + 'tab' => 'Pages', 'order' => 200, - 'label' => 'rainlab.pages::lang.page.manage_pages' + 'label' => 'Manage static pages' ], 'rainlab.pages.manage_menus' => [ - 'tab' => 'rainlab.pages::lang.page.tab', + 'tab' => 'Pages', 'order' => 200, - 'label' => 'rainlab.pages::lang.page.manage_menus' - ], + 'label' => 'Manage static menus' + ], 'rainlab.pages.manage_content' => [ - 'tab' => 'rainlab.pages::lang.page.tab', + 'tab' => 'Pages', 'order' => 200, - 'label' => 'rainlab.pages::lang.page.manage_content' + 'label' => 'Manage static content' ] ]; } + /** + * registerNavigation for the backend, a single item hosting the Vue Editor shell. + */ public function registerNavigation() { return [ 'pages' => [ - 'label' => 'rainlab.pages::lang.plugin.name', - 'url' => Backend::url('rainlab/pages'), + 'label' => 'Pages', + 'url' => Backend::url('rainlab/pages/index'), 'icon' => 'icon-files-o', 'iconSvg' => 'plugins/rainlab/pages/assets/images/pages-icon.svg', 'permissions' => ['rainlab.pages.*'], 'order' => 200, - 'useDropdown' => false, - - 'sideMenu' => [ - 'pages' => [ - 'label' => 'rainlab.pages::lang.page.menu_label', - 'icon' => 'icon-files-o', - 'url' => 'javascript:;', - 'attributes' => ['data-menu-item'=>'pages'], - 'permissions' => ['rainlab.pages.manage_pages'] - ], - 'menus' => [ - 'label' => 'rainlab.pages::lang.menu.menu_label', - 'icon' => 'icon-sitemap', - 'url' => 'javascript:;', - 'attributes' => ['data-menu-item'=>'menus'], - 'permissions' => ['rainlab.pages.manage_menus'] - ], - 'content' => [ - 'label' => 'rainlab.pages::lang.content.menu_label', - 'icon' => 'icon-file-text-o', - 'url' => 'javascript:;', - 'attributes' => ['data-menu-item'=>'content'], - 'permissions' => ['rainlab.pages.manage_content'] - ] - ] + 'useDropdown' => false ] ]; } - public function registerFormWidgets() + /** + * registerMarkupTags adds the staticPage filter, preserved for theme compatibility. + */ + public function registerMarkupTags() { return [ - FormWidgets\PagePicker::class => 'staticpagepicker', - FormWidgets\MenuPicker::class => 'staticmenupicker', + 'filters' => [ + 'staticPage' => [\RainLab\Pages\Classes\Page::class, 'url', false] + ] ]; } + /** + * boot wires the frontend routing and rendering of static pages, preserved from the original. + */ public function boot() { Event::listen('cms.router.beforeRoute', function($url) { @@ -134,15 +145,15 @@ public function boot() Event::listen('cms.pageLookup.listTypes', function() { return [ - 'static-page' => 'rainlab.pages::lang.menuitem.static_page', - 'all-static-pages' => ['rainlab.pages::lang.menuitem.all_static_pages', true] + 'static-page' => 'Static page', + 'all-static-pages' => ['All static pages', true] ]; }); Event::listen('pages.menuitem.listTypes', function() { return [ - 'static-page' => 'rainlab.pages::lang.menuitem.static_page', - 'all-static-pages' => 'rainlab.pages::lang.menuitem.all_static_pages' + 'static-page' => 'Static page', + 'all-static-pages' => 'All static pages' ]; }); @@ -174,7 +185,7 @@ public function boot() Event::listen('backend.richeditor.listTypes', function () { return [ - 'static-page' => 'rainlab.pages::lang.menuitem.static_page', + 'static-page' => 'Static page', ]; }); @@ -193,18 +204,8 @@ public function boot() } /** - * Register new Twig variables - * @return array + * clearCache flushes the router and menu caches for the edit theme. */ - public function registerMarkupTags() - { - return [ - 'filters' => [ - 'staticPage' => [\RainLab\Pages\Classes\Page::class, 'url', false] - ] - ]; - } - public static function clearCache() { $theme = Theme::getEditTheme(); @@ -213,6 +214,5 @@ public static function clearCache() $router->clearCache(); StaticPage::clearMenuCache($theme); - // SnippetManager::clearCache($theme); } } diff --git a/assets/css/editor.css b/assets/css/editor.css new file mode 100644 index 00000000..92155614 --- /dev/null +++ b/assets/css/editor.css @@ -0,0 +1,67 @@ +/* Content-region tabs use the shared backend-tabs component with the + "primary" style (modules/backend/vuecomponents/tabs). No plugin CSS needed. */ + +/* Menu editor: tree list + item form panel. */ +.pages-menu-editor .menu-item-row { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: 4px; + cursor: pointer; + user-select: none; +} + +.pages-menu-editor .menu-item-row:hover { + background: var(--oc-list-hover-bg, rgba(0, 0, 0, .03)); +} + +.pages-menu-editor .menu-item-row.selected { + background: var(--oc-primary-color, #6a70f2); + color: #fff; +} + +.pages-menu-editor .menu-item-row.selected .menu-item-subtitle { + color: rgba(255, 255, 255, .8); +} + +.pages-menu-editor .menu-item-icon { + flex: 0 0 auto; + width: 18px; + text-align: center; + opacity: .7; +} + +.pages-menu-editor .menu-item-label { + flex: 1 1 auto; + min-width: 0; +} + +.pages-menu-editor .menu-item-title { + display: block; + font-size: 13px; + line-height: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pages-menu-editor .menu-item-subtitle { + display: block; + font-size: 11px; + line-height: 14px; + color: var(--oc-text-muted-color, #97a1ab); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pages-menu-editor .menu-item-drag { + cursor: grab; + opacity: .35; + flex: 0 0 auto; +} + +.pages-menu-editor .menu-item-row.drop-target { + box-shadow: inset 0 2px 0 var(--oc-primary-color, #6a70f2); +} diff --git a/assets/css/pages.css b/assets/css/pages.css deleted file mode 100644 index 4e5bf28c..00000000 --- a/assets/css/pages.css +++ /dev/null @@ -1 +0,0 @@ -.control-filelist.menu-list li>a{position:relative}.control-filelist.menu-list li>a:before{background-image:url(../images/menu-icons.png);background-position:0 0;background-repeat:no-repeat;background-size:36px auto;content:" ";height:18px;left:17px;position:absolute;top:18px;width:18px}.control-filelist.menu-list li.active>a:before,.control-filelist.menu-list li>a:hover:before{background-position:0 -60px}.control-filelist.content li>a{position:relative}.control-filelist.content li>a:before{background-image:url(../images/content-icons.png);background-position:0 0;background-repeat:no-repeat;background-size:34px auto;content:" ";height:22px;left:18px;position:absolute;top:10px;width:18px}.control-filelist.content li.active>a:before,.control-filelist.content li>a:hover:before{background-position:0 -27px}.control-filelist.content li.group ul li>a:before{left:34px}.control-filelist.snippet-list li>a{color:#808c8d;position:relative}.control-filelist.snippet-list li>a:before{background-image:url(../images/snippet-icons.png);background-position:0 0;background-repeat:no-repeat;background-size:34px auto;content:" ";height:19px;left:18px;position:absolute;top:13px;top:12px;width:17px}.control-filelist.snippet-list li>a:hover:before{background-position:0 -21px}.control-filelist.snippet-list li.group ul li>a:before{left:34px}@media only screen and (-moz-min-device-pixel-ratio:1.5),only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){.control-filelist.menu-list li>a:before{background-position:0 -11px;background-size:18px auto}.control-filelist.menu-list li.active>a:before,.control-filelist.menu-list li>a:hover:before{background-position:0 -40px}.control-filelist.content li a:before{background-position:0 -27px;background-size:17px auto}.control-filelist.content li a:hover:before,.control-filelist.content li.active a:before{background-position:0 -52px}.control-filelist.snippet-list li a:before{background-position:0 -21px;background-size:17px auto}.control-filelist.snippet-list li a:hover:before{background-position:0 -41px}}.fancy-layout .pagesTextEditor{border-left:1px solid #cfd7e1!important}.control-richeditor [data-snippet]:before{content:attr(data-name)}.control-richeditor [data-snippet]:after{background-image:url(../images/snippet-icons.png);background-position:0 0;background-repeat:no-repeat;background-size:34px auto;content:" ";height:19px;left:18px;left:11px;position:absolute;top:13px;top:12px;width:17px}.control-richeditor [data-snippet].loading:after{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite;background-image:url(../images/loader-transparent.svg);background-position:50% 50%;background-size:15px 15px;content:" ";height:15px;position:absolute;top:13px;width:15px}@media only screen and (-moz-min-device-pixel-ratio:1.5),only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){.control-richeditor [data-snippet]:after{background-position:0 -21px;background-size:17px auto}} diff --git a/assets/css/treeview.css b/assets/css/treeview.css deleted file mode 100644 index 796865e0..00000000 --- a/assets/css/treeview.css +++ /dev/null @@ -1 +0,0 @@ -.control-treeview{margin-bottom:40px}.control-treeview ol{background:var(--oc-primary-bg,#fff);list-style:none;margin:0;padding:0}.control-treeview ol>li{transition:width 1s}.control-treeview ol>li>div{background:var(--oc-primary-bg,#fff);border-bottom:1px solid var(--bs-tertiary-bg);font-size:14px;font-weight:400;position:relative}.control-treeview ol>li>div>a{box-sizing:border-box;color:var(--oc-primary-color,#2b3e50);display:block;line-height:150%;padding:11px 45px 10px 61px;text-decoration:none}.control-treeview ol>li>div:before{background-image:url(../images/treeview-icons.png);background-position:0 -28px;background-repeat:no-repeat;background-size:42px auto;content:" ";height:22px;left:28px;position:absolute;top:15px;width:21px}.control-treeview ol>li>div span.comment{color:#95a5a6;display:block;font-size:13px;font-weight:400;margin-top:2px;overflow:hidden;text-overflow:ellipsis}.control-treeview ol>li>div>span.expand{background-color:transparent;border:0;color:transparent;color:#bdc3c7;cursor:pointer;display:none;font:0/0 a;height:20px;left:2px;position:absolute;text-shadow:none;top:19px;transition:transform .1s ease;width:20px}.control-treeview ol>li>div>span.expand:before{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f0da";font-family:octo-icon!important;font-family:FontAwesome,octo-icon;font-size:15px;font-style:normal;font-variant:normal;font-weight:400;left:8px;line-height:1;line-height:100%;position:relative;text-transform:none;top:2px}.control-treeview ol>li>div>span.drag-handle{background-color:transparent;border:0;bottom:0;color:transparent;color:#bdc3c7;cursor:move;font:0/0 a;height:19px;opacity:0;position:absolute;right:9px;text-shadow:none;transition:opacity .4s;width:18px}.control-treeview ol>li>div>span.drag-handle:before{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f0c9";font-family:octo-icon!important;font-family:FontAwesome,octo-icon;font-size:18px;font-style:normal;font-variant:normal;font-weight:400;line-height:inherit;text-transform:none}.control-treeview ol>li>div span.borders{font-size:0}.control-treeview ol>li>div>ul.submenu{background-color:#7476f8;border-bottom-left-radius:8px;border-bottom-right-radius:8px;bottom:-26.9px;box-shadow:inset 0 3px 3px -3px rgba(0,0,0,.2);display:none;height:27px;left:20px;list-style:none;margin-left:15px;padding:0;position:absolute;z-index:200}.control-treeview ol>li>div>ul.submenu [data-control=create-object]{padding-left:15px;padding-right:15px}.control-treeview ol>li>div>ul.submenu li{font-size:12px}.control-treeview ol>li>div>ul.submenu li a{color:#fff;display:block;outline:none;padding:4px 3px 0;text-decoration: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:active>ul.submenu{background-color:var(--oc-selection,#5254f6)}.control-treeview ol>li>div .checkbox{position:absolute;right:0;top:-2px}.control-treeview ol>li>div .checkbox label{margin-right:0}.control-treeview ol>li>div .checkbox label:before{border-color:#ccc}.control-treeview ol>li>div.popover-highlight{background-color:var(--bs-primary,#6a6cf7)!important}.control-treeview ol>li>div.popover-highlight:before{background-position:0 -80px}.control-treeview ol>li>div.popover-highlight>a{color:#fff!important;cursor:default}.control-treeview ol>li>div.popover-highlight span{color:#fff!important}.control-treeview ol>li>div.popover-highlight>span.drag-handle,.control-treeview ol>li>div.popover-highlight>ul.submenu{display:none!important}.control-treeview ol>li.dragged div,.control-treeview ol>li>div:hover{background-color:var(--bs-primary,#6a6cf7)!important}.control-treeview ol>li.dragged div>a,.control-treeview ol>li>div:hover>a{color:#fff!important}.control-treeview ol>li.dragged div:before,.control-treeview ol>li>div:hover:before{background-position:0 -80px}.control-treeview ol>li.dragged div:after,.control-treeview ol>li>div:hover:after{bottom:0!important;top:0!important}.control-treeview ol>li.dragged div span,.control-treeview ol>li>div:hover span{color:#fff!important}.control-treeview ol>li.dragged div span.drag-handle,.control-treeview ol>li>div:hover span.drag-handle{cursor:move;opacity:1}.control-treeview ol>li.dragged div span.borders,.control-treeview ol>li>div:hover span.borders{display:none}.control-treeview ol>li>div:active{background-color:var(--oc-selection,#5254f6)!important}.control-treeview ol>li>div:active>a{color:#fff!important}.control-treeview ol>li[data-no-drag-mode] div:hover span.drag-handle{cursor:default!important;opacity:.3!important}.control-treeview ol>li.dragged li.has-subitems>div:before,.control-treeview ol>li.dragged.has-subitems>div:before{background-position:0 -52px}.control-treeview ol>li.dragged div>ul.submenu{display:none!important}.control-treeview ol>li>ol{padding-left:20px;padding-right:20px}.control-treeview ol>li[data-status=collapsed]>ol{display:none}.control-treeview ol>li.has-subitems>div:before{background-position:0 0;height:26px;left:26px;width:23px}.control-treeview ol>li.has-subitems>div.popover-highlight:before,.control-treeview ol>li.has-subitems>div:hover:before{background-position:0 -52px}.control-treeview ol>li.has-subitems>div span.expand{display:block}.control-treeview ol>li.placeholder{opacity:.5;position:relative}.control-treeview ol>li.dragged{opacity:.25;position:absolute;z-index:2000}.control-treeview ol>li.dragged>div{border-radius:3px}.control-treeview ol>li.drop-target>div{background-color:#2581b8!important}.control-treeview ol>li.drop-target>div>a,.control-treeview ol>li.drop-target>div>a>span.comment{color:#fff}.control-treeview ol>li.drop-target>div:before{background-position:0 -80px}.control-treeview ol>li.drop-target.has-subitems>div:before{background-position:0 -52px}.control-treeview ol>li[data-status=expanded]>div>span.expand{transform:rotate(90deg) translate(0)}.control-treeview ol>li.drag-ghost{background-color:transparent;box-sizing:content-box}.control-treeview ol>li.active>div{background:var(--oc-selection,#5254f6)}.control-treeview ol>li.active>div>a,.control-treeview ol>li.active>div>a>span.comment,.control-treeview ol>li.active>div>a>span.expand,.control-treeview ol>li.active>div>span.expand{color:#fff}.control-treeview ol>li.active>div>span.borders:after,.control-treeview ol>li.active>div>span.borders:before{background-color:var(--oc-selection,#5254f6);content:" ";display:block;height:1px;left:0;position:absolute;width:100%}.control-treeview ol>li.active>div>span.borders:before{top:-1px}.control-treeview ol>li.active>div>span.borders:after{bottom:-1px}.control-treeview ol>li.active>div:before{background-position:0 -80px}.control-treeview ol>li.active.has-subitems>div:before{background-position:0 -52px}.control-treeview ol>li.no-data{color:#666;font-size:14px;font-weight:400;margin:0;padding:18px 0;text-align:center}.control-treeview ol>li>ol>li>div{margin-left:-20px;margin-right:-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}.control-treeview ol>li>ol>li>div>span.expand{left:12px}.control-treeview ol>li>ol>li>ol>li>div{margin-left:-40px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>div>span.expand{left:22px}.control-treeview ol>li>ol>li>ol>li>ol>li>div{margin-left:-60px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>div>span.expand{left:32px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-80px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:42px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-100px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:52px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-120px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:62px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-140px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:72px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-160px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:82px}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div{margin-left:-180px;margin-right:-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}.control-treeview ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>ol>li>div>span.expand{left:92px}.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{margin-left:-200px;margin-right:-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}.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>span.expand{left:102px}.control-treeview p.no-data{color:#666;font-size:14px;font-weight:400;margin:0;padding:18px 0;text-align:center}.control-treeview a.menu-control{border:2px dotted #ebebeb;border-radius:5px;color:#bdc3c7;display:block;font-size:12px;font-weight:600;margin:20px;padding:13px 15px;text-transform:uppercase;vertical-align:middle}.control-treeview a.menu-control:focus,.control-treeview a.menu-control:hover{background-color:var(--bs-primary,#6a6cf7);border:none;color:#fff;padding:15px 17px;text-decoration:none}.control-treeview a.menu-control:active{background:var(--oc-selection,#5254f6);color:#fff}.control-treeview a.menu-control i{font-size:14px;margin-right:10px}.control-treeview.treeview-light{margin-bottom:0;margin-top:20px}.control-treeview.treeview-light ol{background-color:transparent}.control-treeview.treeview-light ol>li>div{background-color:transparent;border-bottom:none}.control-treeview.treeview-light ol>li>div:before{top:15px}.control-treeview.treeview-light ol>li>div>a{padding-bottom:10px;padding-top:10px}.control-treeview.treeview-light ol>li>div span.expand{top:19px}.control-treeview.treeview-light ol>li>div>span.drag-handle{background:#8284f8;bottom:auto;height:100%;right:0;top:0;transition:none!important;width:60px}.control-treeview.treeview-light ol>li>div>span.drag-handle:before{left:50%;margin-left:-6px;position:absolute;top:50%}.control-treeview.treeview-light ol>li>div>ul.submenu{background:transparent;bottom:auto;font-size:0;height:100%;left:auto;margin:0;right:60px;top:0;white-space:nowrap}.control-treeview.treeview-light ol>li>div>ul.submenu:after,.control-treeview.treeview-light ol>li>div>ul.submenu:before{display:none}.control-treeview.treeview-light ol>li>div>ul.submenu li{background:#8284f8;border-right:1px solid var(--bs-primary,#6a6cf7);display:inline-block;height:100%}.control-treeview.treeview-light ol>li>div>ul.submenu li p{display:table;height:100%;margin:0;padding:0}.control-treeview.treeview-light ol>li>div>ul.submenu li p a{box-sizing:border-box;display:table-cell;font-size:13px;height:100%;padding:0 20px;vertical-align:middle}.control-treeview.treeview-light ol>li>div>ul.submenu li p a i.control-icon{font-size:22px;margin-right:0}body.dragging .control-treeview ol.dragging,body.dragging .control-treeview ol.dragging ol{background:#ccc;padding-right:20px;transition:padding 1s}body.dragging .control-treeview ol.dragging ol>li>div,body.dragging .control-treeview ol.dragging>li>div{margin-right:0;transition:margin 1s}body.dragging .control-treeview ol.dragging ol>li>div .custom-checkbox,body.dragging .control-treeview ol.dragging>li>div .custom-checkbox{opacity:0;transition:opacity .5s}body.dragging .control-treeview.treeview-light ol.dragging ol>li>div,body.dragging .control-treeview.treeview-light ol.dragging>li>div{background-color:#f9f9f9}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){.control-treeview ol>li>div:before{background-position:0 -79px;background-size:21px auto}.control-treeview ol>li.has-subitems>div:before{background-position:0 -52px}.control-treeview ol>li.has-subitems.active>div:before,.control-treeview ol>li.has-subitems>div.popover-highlight:before,.control-treeview ol>li.has-subitems>div:hover:before{background-position:0 -102px}.control-treeview ol>li.active>div:before,.control-treeview ol>li.dragged li>div:before,.control-treeview ol>li.dragged>div:before,.control-treeview ol>li>div.popover-highlight:before,.control-treeview ol>li>div:hover:before{background-position:0 -129px}.control-treeview ol>li.dragged li.has-subitems>div:before,.control-treeview ol>li.dragged.has-subitems>div:before{background-position:0 -102px}.control-treeview ol>li.drop-target>div:before{background-position:0 -129px}.control-treeview ol>li.drop-target.has-subitems>div:before{background-position:0 -102px}} diff --git a/assets/images/content-icons.png b/assets/images/content-icons.png deleted file mode 100644 index 893dc71ae0ea0765a3042b0f5df8f398635bbcab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1761 zcmbVNc~BE)6c31qsECe7p%vGSRkS48%>iUdz`zm{O9Dm)vDS{8-3_deY`R%U0!0~c z5FA^RDi-g~G~%du*2<_T3V1^681Vv+dQ}}^TBty+ZS97`_7BJDbZ2(I?|t9=V*Myo*$+zN7UWl$jZRhCDN<(4ouIg?#Ln^-%>kUSMZ8%aQG#@l9a9A-shhzaGnMGNY;LpeFL4YEZ z;6j-W(pi(pY)VtWkP8a*>3Bg7E+@c*ctGJmI0I&q#Q=vn*J48)O0eH9!tH&>A`s|T zVRMw=0I5t}8jwUYBp?&Y1UMvyff%_EmPzF>90iCWSR#TUkytK(VMH2(z|p|q19ILN z!h{%5^`I|KQi9nmYehsNyWK9dON2C&C4%L0xd;-A#9{%bA+R|uEanhcZ2Tb$C~3nP z%F0r-1@KwKjC3BW1UaSyA(*W?-D|@Z+hC%&l!+XeRRjwmk=g8vt6$s38pwZWyiwbh z?zEC318Jl47@Vtzi9ZD9a`$#eK1GfVlFCqAQLtQ;#`Daih1H@;kh>8Q6oEi;JX#`= zMGGXRm}r3%!leQu0f_~Wk-!K{EQLs;WXR5&@ajZ$Y_u#!B^4*5Fq|xnl_$zn(pX3( z5z7)|VfheNYq7DI1t*7mQ=IQXto*H5B#9w0mS)mvI(MiG(z0omwq?^+ASnrm)L}Sf z@yWA%H5v#RB^fH8B-9LT2KtkXP;Vgc-+~{&6aP1PB94^E*D3$%oS`GG9evx^bHE9& zCyBIhlHcZ5^AM^%GPR%J!(i9~+wIeI@)XSa-u3cB?A@ogV%m z_-Og+kq6RnB5mRLuHL}a!iRoP{K^j&F4L^p6uHdR{odY+N_`*OlU8)B^F?a+&N!t^ zc6SSsw|n=V=M4rtX;xn1j&{4(>Bal*{n6?P#$>5MMqIXq|nNdf71?`3oE@;GTqhAXmV z7pTg#g|uY50bxsRC}7!t`FJKuRjizG=;8CO@=FaL<29!l|K^dL*MJZ8QF8c+pNDZ0 z9OcC8g&w}&vm*NXg=f_8&F$ZHxGPVEUc6STMmFxCLLNWtJmJ~txhHPY#5~TNj0ykiatBGzU2oqmjT+&ib1#cbwbwzW2;KRgVL%%v3(PZCEk33B@AvhyOKA z&#W?Byj4q@wq~76EbW+C)bXU^@?@_mY>q<_HuHp_F}-W})9kbJ7vtTdUGwUuc0JwQ zSzRR6Wi^$nuK;S-TwaO7-!%s|A}6W%^~Lx))er7F6N66I7fp)GGaP#7uh{Ds&Of}VVNA$~j1iN?2s zSBvy?Spe05ex6zoqAfijY-)Wa3zq!OM`M|_*JKdqw>ek zmK2swskx|Mv%9u*V$4)Ga+HFcftp8mXW#!`Vw|p7{0-%r>zW(NKhNiNm5=LKij8P) f+die{uK#G>sYa5yox82i_tViP>(PCQOH2L+WNV)& diff --git a/assets/images/loader-transparent.svg b/assets/images/loader-transparent.svg deleted file mode 100644 index cf84589e..00000000 --- a/assets/images/loader-transparent.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - -]> - - - - - - - - diff --git a/assets/images/menu-icons.png b/assets/images/menu-icons.png deleted file mode 100644 index f3641e9f24aab7a57406091765be3eb47424823f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1198 zcmbVMO>Epm6m~!=v}%+Da3LM-35u!%zpo-{% z2PRPw0ef!tTQp2jPsVP!f-BaT4g-$^i4NEFLV~8K;gMzt;55Q?6-~N+hPl6dm7!fb z!@QEad2N9wbPGO-SiL|P5q9SXec$k(1F~y4lFKMhO>awbfLv;ILh_}dg^rCUJ z?TbhmriO8-^L(Sx;2J3|h$eYa(==X?cu8W31{=@$7&KWw9_UywPz)nC#BSiziA7Ki z>Nvv?rkf#np=E6u_TzS8x_%-LCN|xfg7w>cFR^i{1t;RDSto9$f z`TBse#I=6At=T=ZcYgX=y4I7u@bOUnt+CSky>!<6VfA^*Vx{bb_tf>NHx|cAjm2Nj z|K9)QU!zl*@~Nri{sa5IX#H_6tlhcw=FOhsgF#lhbmbeW>%{fdzMb-?JG-7Jua9}} qW=o5^dzbE6SJr-EUV7@!XU|aX!v3!=UR^zy{6PxlxbaEu%)&$WHIs?} diff --git a/assets/images/snippet-icons.png b/assets/images/snippet-icons.png deleted file mode 100644 index 3d0b66f89ef9a75556e945a08418495390de1b9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1436 zcmaJ>eN5D57_WgbMquiqOJvDbdoB}PukC%eaI(8A=%IJwLHOF7>$UG4$8qi17Vl%s zKa2>7;IfdJ90SbAbc-MfZc8Q(ROXCJ7Mx=+SVA0=7^7s%oC`yV>jB612eD22zR%m= z@A-b(ROH-|k(QYTK~RQ$C+$*JM0vugDa!k#3ocfcO>%y@Tr7Cx5F-M}!U=8w+XIXj zxB$aN4qOMh5R@eNO3LMO$8L%h0%|6XQHKLTg$+TudEp?#`hg6)f!D{Ik>3Y~5ZK3= zkutplcLZ&q%C|Erg5s#NgpK-Hl0)*g!?|HfQ4jz!1BU}OyhMe~$f90K*~gDD1YU&5 zelxPBUcL~;n#g%5 z7hSMyOOeb-l`ID-3=4%q>X23~h+d2!NfN^~m_~yt2vmyjG80C5NtMt*1Bn%VLD?tp za9ktf7HVZPqIkL#LLlgHtSIKCb`Hxvgd-cWl)pHdX2h8Eb`0N`aiZAO$YYR<<|ILR8cTD<|)dW;5C zhqF4=&EXmpcXJHKXmmJmYZH1_<6CV6O+sj!NfT>Cl>~U)CJ!(fJS?)z=T=ExfwjP7 z&>Jl}O+FnD-b7mU7M%&VXf=APi69Y7aR`fd$^W`0aip|je7iCOim)<8fLBIGRA%Ik z_;3`0)(qQeOG$Y0e&wuV7+?2w^Tp(WOHfDmZYT+QlJ$}tYugLgF!!@hWgjaP2j`A2 zIQwIVOp%A?uCD!8M*12$8+rT7XU<$}$SFLoUZ0%xS6;&-GWE%^@q>rRixF2^=zyy~ z-5AMRXvlpqF%zo(VejT&UwnL2g8nH}eNeCBFJGUCj=j}ROzb$)YZ6=5PQ_Fe1>U=j z8wmfO;+CGlvpIfs8{houzRKx6O?SfM(3Pq7=jllE+sQXi{8%~}?H#SUeN=z1@isR| zwZ4&STe*!DH{j0AonG<giSO>OMzJZ}21;ZDT{} znV&uGJE@wTXummst9_=idMKMbO!b@BIcA>O*!N-P**72Lf8yB+T`Nl#Us;=OsXOvz z!wB{4mieBpw!cqV3!BHMu8w|_x??r`C;K z@MhGNI$AGz_r>0w>&afYb>47weQNjowrzE#b3N=w?~Ms-QXq$`qk3`-Y>)ph?fFjn Job{c<{{kap390}9 diff --git a/assets/images/treeview-icons.png b/assets/images/treeview-icons.png deleted file mode 100644 index a08c70b88675eaaffa84fb2e6e29a80e9455de3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1344 zcmeAS@N?(olHy`uVBq!ia0vp^S_}+~dK}C^*7@HBr-2;n0G|-ohMwO4|Nl>2x(vdg z3JEj~XeOpn5J60Fl0+beBU^##c(^Pc238m1atWHtA;zY)31|XCGp!`ZFBnKOGV=2a z3fkE@JG;2Jy1Kc!yL)(ediweX1O&vzB_t#!CMD(P7Zem078jS4l$Ms4S5{V6cXS*% za^&dIW5#fw+3UVZrR@#EL8-@bkS{^Q5bpMU=R{rm5qz?Fs@ z3=GVEo-U3d6>)E_cxD|j5NJ)D%0KDLgct?o66rU07EQkQg<;aW`X&#qM>kogYHbKV zX0MQIw;=3%;HwAO_SQn$R~G-?>@$1H_m`UgyZ68E*;4sg{N%1=jzovecR5T9yyX9V zQn={kFxgfz!*}`I-fKb;Jh7Vo%h=VdC-QMKCU8AH`Q^lu=|0k8XPw%sp9cNEa4|0P z%G7_~PI<)M(^fuPmb`?odg+BEf3~R0ndwV13`>^U%v@E?dTMcG#<$!xbH8)-zWBBz zQ0wRqsr+i?rQa*2uF)=UO4}`(kut^Z@z=mbi+W4P7PxR)Z;OD}_UEckqRs$M?3Ot#eD)bo5% zQf&tf`t}!gbes^-RC*iP#v|yz`;F2j!_1XHk&h~GzyD|Z)@!Kv!_1$9mF-Er=9c~5 z`Tia|U;o>#trArp6d%s?_K6+0&s@=!zjdwTuDm|`J;7ctSwpwyD}Rm8&zFHwA71}; zGCq`MP-};icYTB)N{9D{Z?q*htIbYqoZTT!C&Va86z8SjQ_xl&M zkvCv{=8HAG%S!&TthD*Z-;*{gbJ+ruDCL_`QF(`of~8(5t^Yc~i1AcBtJYK>$ILB` zpN?M2lAd<^)FtH!L7F8goSu&}b2vFmO714STH|r@nXaPGU73Hg|p5nw)-Wm>jmo3~Z7QDShVXCzO(88Hbmo(y>rae+MIcj^xzq;%|qk%wM zVU7M|6;Gp1#uF-@3KA;EB!E1HcfaeK@-DyYjr%_5CdYS%9|!q%??3zSbNZas_n%I5 z;Z}XSQ((oU?;QrI+Ch(aw^*%@yLIom-V~v29YKq}5y*?F}EnRcYSajva%Uxop z{4#@YcP`@!ymhVYjZ5&&8$OCIqknL$m^Z%by|4Ir++^+lKjj^0}_{J^E}SGC%!%ePtr2VXNv9kt@9}8+j8T>}dkQ z5l%i(2_Pp1%1K#x|8+;3>arrvum8(FUJ3U9d-gBemNTx#3MN|ufq9t0)78&qol`;+ E0BLsAIRF3v diff --git a/assets/images/treeview-submenu-tabs.png b/assets/images/treeview-submenu-tabs.png deleted file mode 100644 index 6c39867bada1d1380af79023b63857b8d905e2e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2714 zcmb`JS5y;N9>#Boir_FvK$<8ZQiLEul#ZyN2%$;~RRjg;CDhPjLsRrM`bKV7VlA>dy56%N>nP4dKn9Il)Nby>UL^#j+*D+MI%xhu$6EUDFKk zy&}wz!5*7UL6@J!t^qx`_kAgda^#FH+?A!7`XTZ)#5Xp8?buWg-n4mlcEKD9rnYZ; zf#8-Ye+{ENWEh(9k2_y91y{mu@wYU#@AxZ4`^dGf=|Y+TF#R0?QY9^gN1)4z_ZF<} z^&St~o8t_C^SE;dBK)Tyt&f=cdw7Yrf{Rp^9^C(P>#8Gp=y~%7JUQ5W(eTatQXmu7{J$)#&s5Tp94IdT>oA{ZCHcb)a3OOg$<3) zyZhA;)1O5eCx+6eq>%lmZd%?*K(5i^_^u~RQ%%iYf#)r5E zY`j3b?|dCs&4rJ!Kt-nTl8{~r?>_3{!9?2;@UeVqiqrZZx^tp=Vk!1BM!T-OvJI|H zIkD4^@(WJ~p`QmV>SIQ}*8LHqeReTRyz9IXt4pJ0TpaCIx9DW&dRVj%CH#q6(9OROrU zqiADXI9=a|A|RhkOhZJqiJP{ctVFVY(XZKLWjsGbo2xs zfm%3^tS`Uwl2;-Z7>Wp8sj}^v-~d5Hq~ImLfdk-kMmQ=5RZ- z6534lEg52^P#WMXqMd}IqM%UQqHpv0z8fQEW#0@MfXAQ^=x_-1lAfaUz}F^^ueedk zizCSr=G(px>*t)=zP&^$V(Z4VeuG9@i2eT&^aJ&C-kGkLCC~vz(o)>3YjC$5zH>5U!#@Te%26GHVWK~foS@;{& z0eXe!N~s6e(ebBqNoy-%s{&XEq2Z>Erw?Z3V}bts5$qW(OLc_4MIuh=E{0xX^|fjt zml66QK$d(RgbezDii&#$1)dkIHPF_?IBOiO>&JGrI7-hVNXBhdM9)L8*&WCLdw_bA zRc(bQ*}fndxaYR@Jdhz)W#-W*o84OOxt6+Dw9T`2Nj7-)oR+)0S%lXP%|y{H%LL;o z5ZjPuc5JFphS++qV(Ieijra(XTfdA2>p9ecBi8!b_3vlf^RlWx-1o9r8mis!&g*?^$mKlnI-2ka;x(+5>Tr!5g=0I+&Hm1n?u2aNBHf-|`o-rydV9g;O zbRGSct8C5NG?$V*HsF$Z8ws6)(O|sqU(4oWx9Rf+P!PQq@BJ^HsbPAJgOZ-+wBQg? z=rjg(!>SS;Qcxx$q7;2;#rY*_ZJic54wVb@SCD)C&a+gSECJBjnev#hKY6ykwLyA- z`DlDO1vQ^&#FHseMhaJ_o`+LA`Wh7@UcKX&hMO^~iZn_9wNb+iRgithbmJ5b!mm@@ z7X-Ho5+w>;#WCg(0K1~=>^Qhi2*H=AAv9d}liJ@`NCXWR4sNlE*NvZhUg1Cb!2jN% zx{FOatGJ^u^~8*HL|AF_YQR=~|0cUwU16*U`F41SD1yv_jTxkQmD&h6v8iR)GqG0L zKPvM0SrVMV&&M#6oi3d@*aw$88k4wsUg(vsO735b!;e;tcfF5wyl}!-aSqkmadyh1 zgD53o$}zbP+3IoaJ>;ErSf_-tWc8&(8_hj`{{E?A&F*&SMEve$M%OyQ9A7z&zfmOC zWTeq&Cps}0J&Yp_R$*+3>fB)mawL)(+|{Lg=KbbFLg5b;De~pO%lsx`gbV^A>Ycc0 zKKxmf>6WsRN)Ds;H@u@Jf^7XngKlshl(vpn#iP=E@zn~-j`u!>?iw_7a|0tIZ(|%q zxp?gRr`c!U-z;Thn%)iJ3}2Le&J4($FY&7+C*5KFveL#u$b|M=JvxehA~H7klTcA( z{mB(+(hbe~t6`I;a3rsx4;hk1YJl)!aowzsw1te(IZ21GT8|!W=d`pa0H|{4gXcpN?Q67G7jjO5CxCrmN8U=8eJp`W8h5_b z>nC=RZtrV{^x?WCpn>`B8Fy9jE&59ms-#<9~MLo({BG_j4X&DMERn7H~uHt)FXC}Aq5@( ze`sTpXO5RVX^8NHYt1$+s9 W%d8tG7McG_pnKg=`?qTjxPJmA&?X}Q diff --git a/assets/js/october.treeview.js b/assets/js/october.treeview.js deleted file mode 100644 index 1ed99120..00000000 --- a/assets/js/october.treeview.js +++ /dev/null @@ -1,449 +0,0 @@ -/* - * TreeView Widget. Represents a sortable and draggable tree view. This widget was first used in the Pages plugin, for the sidebar page tree. - * - * Data attributes: - * - data-group-status-handler - AJAX handler to execute when an item is collapsed or expanded by a user - * - data-reorder-handler - AJAX handler to execute when items are reordered - * - * Events - * - open.oc.treeview - this event is triggered on the list element when an item is clicked. - * - * Dependences: - * - Tree list (october.treelist.js) - * - */ -+function ($) { "use strict"; - var Base = $.oc.foundation.base, - BaseProto = Base.prototype - - var TreeView = function (element, options) { - this.$el = $(element) - this.options = options - this.$allItems = null - this.$scrollbar = null - - Base.call(this) - - $.oc.foundation.controlUtils.markDisposable(element) - this.init() - } - - TreeView.prototype = Object.create(BaseProto) - TreeView.prototype.constructor = TreeView - - TreeView.prototype.init = function () { - this.$allItems = $('ol > li', this.$el) - this.$scrollbar = this.$el.closest('[data-control=scrollbar]') - - /* - * Init the sortable - */ - - this.initSortable() - - /* - * Create expand/collapse icons and drag handles - */ - - this.createItemControls() - - /* - * Bind the click events - */ - - this.$el.on('click.treeview', 'li > div > ul.submenu li a', this.proxy(this.onOpenSubmenu)) - this.$el.on('click.treeview', 'li > div > a', this.proxy(this.onOpen)) - this.$el.on('click.treeview', 'li span.expand', this.proxy(this.onItemExpandClick)) - - /* - * Listen for the AJAX updates and dispose the widget - */ - - this.$el.one('dispose-control', this.proxy(this.dispose)) - - /* - * Mark previously active item, if it was set - */ - var dataId = this.$el.data('oc.active-item') - if (dataId !== undefined) { - this.markActive(dataId) - } - - this.$scrollbar.on('oc.scrollEnd', this.proxy(this.onScroll)) - } - - TreeView.prototype.dispose = function() { - this.unregisterHandlers() - this.clearScrollTimeout() - - this.options = null - this.$el.removeData('oc.treeView') - this.$el = null - this.$allItems = null - this.$scrollbar = null - - BaseProto.dispose.call(this) - } - - TreeView.prototype.unregisterHandlers = function() { - this.$scrollbar.off('oc.scrollEnd', this.proxy(this.onScroll)) - this.$el.off('.treeview') - this.$el.off('move.oc.treelist', this.proxy(this.onNodeMove)) - this.$el.off('aftermove.oc.treelist', this.proxy(this.onAfterNodeMove)) - this.$el.off('dispose-control', this.proxy(this.dispose)) - } - - TreeView.prototype.createItemControls = function() { - // Scoped to ol > li to exclude submenu items, using vanilla DOM for performance on large trees - var items = this.$el.get(0).querySelectorAll('ol > li'), - itemCount = items.length - - for (var i = 0; i < itemCount; i++) { - var item = items[i], - container = item.firstElementChild - - if (!container || container.tagName !== 'DIV' || container.querySelector(':scope > span.expand')) { - continue - } - - container.insertAdjacentHTML('afterbegin', 'Expand') - - if (!container.querySelector(':scope > span.drag-handle')) { - var dragTitle = item.hasAttribute('data-no-drag-mode') - ? ' title="Dragging is disabled when the Search is active"' - : '' - - container.insertAdjacentHTML('beforeend', 'Drag') - } - - container.insertAdjacentHTML('beforeend', '') - } - } - - TreeView.prototype.collapseGroup = function($group) { - var $subitems = $('> ol', $group) - - $subitems.css({ - 'overflow': 'hidden' - }) - - $subitems.animate({'height': 0}, { duration: 100, queue: false, complete: function() { - $subitems.css({ - 'overflow': 'visible', - 'display': 'none', - 'height' : 'auto' - }) - $group.attr('data-status', 'collapsed') - $(window).trigger('resize') - } }) - - this.sendGroupStatusRequest($group, 0) - } - - TreeView.prototype.expandGroup = function($group) { - var $subitems = $('> ol', $group) - - $subitems.css({ - 'overflow': 'hidden', - 'display': 'block', - 'height': 0 - }) - - $group.attr('data-status', 'expanded') - $subitems.animate({'height': $subitems[0].scrollHeight}, { duration: 100, queue: false, complete: function() { - $subitems.css({ - 'overflow': 'visible', - 'height': 'auto' - }) - $(window).trigger('resize') - } }) - - this.sendGroupStatusRequest($group, 1); - } - - TreeView.prototype.fixSubItems = function() { - var items = this.$el.get(0).querySelectorAll('ol > li'), - itemCount = items.length - - for (var i = 0; i < itemCount; i++) { - var childList = items[i].querySelector(':scope > ol') - - items[i].classList.toggle('has-subitems', !!(childList && childList.firstElementChild)) - } - } - - TreeView.prototype.toggleGroup = function(group) { - var $group = $(group); - - $group.attr('data-status') == 'expanded' - ? this.collapseGroup($group) - : this.expandGroup($group) - } - - TreeView.prototype.sendGroupStatusRequest = function($group, status) { - if (this.options.groupStatusHandler !== undefined) { - var groupId = $group.data('group-id') - - $group.request(this.options.groupStatusHandler, {data: {group: groupId, status: status}}) - } - } - - TreeView.prototype.sendReorderRequest = function() { - if (this.options.reorderHandler === undefined) - return - - var groups = {} - - function iterator($container, node) { - $('> li', $container).each(function(){ - var subnodes = {} - iterator($('> ol', this), subnodes) - - node[$(this).data('groupId')] = subnodes - }) - } - - iterator($('> ol', this.$el), groups) - - this.$el.request(this.options.reorderHandler, {data: {structure: JSON.stringify(groups)}}) - } - - TreeView.prototype.initSortable = function() { - var $noDragItems = $('[data-no-drag-mode]', this.$el) - - if ($noDragItems.length > 0) - return - - if (this.$el.data('oc.treelist')) - this.$el.treeListWidget('unbind') - - this.$el.treeListWidget({ - tweakCursorAdjustment: this.proxy(this.tweakCursorAdjustment), - isValidTarget: this.proxy(this.isValidTarget), - useAnimation: false, - usePlaceholderClone: true, - handle: 'span.drag-handle', - onDrag: this.proxy(this.onDrag), - tolerance: -20 // Give 20px of carry between containers - }) - - this.$el.on('move.oc.treelist', this.proxy(this.onNodeMove)) - this.$el.on('aftermove.oc.treelist', this.proxy(this.onAfterNodeMove)) - } - - TreeView.prototype.markActive = function(dataId) { - $('ol > li', this.$el).removeClass('active') - - if (dataId) - $('li[data-id="'+dataId+'"]', this.$el).addClass('active') - - this.$el.data('oc.active-item', dataId) - } - - TreeView.prototype.update = function() { - this.$allItems = $('ol > li', this.$el) - this.createItemControls() - this.fixSubItems() - this.initSortable() - - var dataId = this.$el.data('oc.active-item') - if (dataId !== undefined) { - this.markActive(dataId) - } - } - - TreeView.prototype.handleMovedNode = function() { - this.$el.trigger('change') - this.$allItems.removeClass('drop-target') - this.fixSubItems() - this.sendReorderRequest() - } - - TreeView.prototype.tweakCursorAdjustment = function(adjustment) { - if (!adjustment) { - return adjustment - } - - if (this.$scrollbar.length > 0) { - adjustment.top -= this.$scrollbar.scrollTop() - } - - return adjustment - } - - TreeView.prototype.isValidTarget = function($item, container) { - return $(container.el).closest('li').attr('data-status') != 'collapsed' - } - - TreeView.DEFAULTS = { - - } - - // TREEVIEW EVENT HANDLERS - // ============================ - - TreeView.prototype.onOpenSubmenu = function(ev) { - var e = $.Event('submenu.oc.treeview', {relatedTarget: ev.currentTarget, clickEvent: ev}) - this.$el.trigger(e, this) - - return false - } - - TreeView.prototype.onOpen = function(ev) { - var e = $.Event('open.oc.treeview', {relatedTarget: $(ev.currentTarget).closest('li').get(0), clickEvent: ev}) - this.$el.trigger(e, ev.currentTarget) - - return false - } - - TreeView.prototype.onNodeMove = function() { - setTimeout(this.proxy(this.handleMovedNode), 50) - } - - TreeView.prototype.onAfterNodeMove = function(ev, data) { - this.$allItems.removeClass('drop-target') - data.container.el.closest('li').addClass('drop-target') - } - - TreeView.prototype.onItemExpandClick = function(ev) { - this.toggleGroup($(ev.currentTarget).closest('li')) - return false - } - - // TREEVIEW SCROLL ON DRAG - // ============================ - - TreeView.prototype.onScroll = function () { - if (!$('body').hasClass('dragging')) { - return - } - - var changed = this.lastScrollPos - this.$scrollbar.scrollTop() - - this.$el.children('ol').each(function() { - var sortable = $(this).data('oc.sortable') - sortable.refresh() - sortable.cursorAdjustment.top += changed // Keep cursor adjustment in sync with scroll - }); - - this.dragCallback() - - this.lastScrollPos = this.$scrollbar.scrollTop() - } - - TreeView.prototype.onDrag = function ($item, position, _super, event) { - this.lastScrollPos = this.$scrollbar.scrollTop() - - this.dragCallback = function() { - _super($item, position, null, event) - }; - - this.clearScrollTimeout() - this.dragCallback() - - if (!this.$scrollbar || this.$scrollbar.length === 0) - return - - if (position.top < 0) { - this.scrollOffset = -10 + Math.floor(position.top / 5) - } - else if (position.top > this.$scrollbar.height()) { - this.scrollOffset = 10 + Math.ceil((position.top - this.$scrollbar.height()) / 5) - } - else { - return - } - - this.dragScroll() - } - - TreeView.prototype.scrollMax = function() { - return this.$el.height() - this.$scrollbar.height() - } - - TreeView.prototype.dragScroll = function() { - var startScrollTop = this.$scrollbar.scrollTop() - var changed - - this.scrollTimeout = null - - this.$scrollbar.scrollTop(Math.min(startScrollTop + this.scrollOffset, this.scrollMax())) - - changed = this.$scrollbar.scrollTop() - startScrollTop - if (changed === 0) { - return - } - - this.$el.children('ol').each(function() { - var sortable = $(this).data('oc.sortable') - sortable.refresh() - sortable.cursorAdjustment.top -= changed // Keep cursor adjustment in sync with scroll - }); - - this.dragCallback() - - this.$scrollbar.data('oc.scrollbar').setThumbPosition() // Update scrollbar position - - this.scrollTimeout = window.setTimeout(this.proxy(this.dragScroll), 100) - } - - TreeView.prototype.clearScrollTimeout = function() { - if (this.scrollTimeout) { - window.clearTimeout(this.scrollTimeout) - this.scrollTimeout = null - } - } - - // TREEVIEW PLUGIN DEFINITION - // ============================ - - var old = $.fn.treeView - - $.fn.treeView = function (option) { - var args = arguments - - return this.each(function () { - var $this = $(this) - var data = $this.data('oc.treeView') - var options = $.extend({}, TreeView.DEFAULTS, $this.data(), typeof option == 'object' && option) - if (!data) { - $this.data('oc.treeView', (data = new TreeView(this, options))) - } - else if (typeof option !== 'string') { - // Inner contents may have been replaced by an AJAX partial update, - // refresh item controls so expand/drag handles reattach. - data.update() - } - - if (typeof option == 'string' && data) { - var methodArgs = []; - for (var i=1; i div.tab-content > div.tab-pane[data-modified]', this.$masterTabs).each(function(){ - var inputType = $('> form > input[name=objectType]', this).val() - counters[inputType].count++ - }) - - $.each(counters, function(type, data){ - $.oc.sideNav.setCounter('pages/' + data.menu, data.count); - }) - } - - /* - * Triggered when a tab is displayed. Updated the current selection in the sidebar and sets focus on an editor. - */ - PagesPage.prototype.onTabShown = function(e) { - var $tabControl = $(e.target).closest('[data-control=tab]') - - if ($tabControl.attr('id') == 'pages-master-tabs') { - var dataId = $(e.target).closest('li').attr('data-tab-id'), - title = $(e.target).attr('title') - - if (title) - this.setPageTitle(title) - - this.$pageTree.treeView('markActive', dataId) - $('[data-control=filelist]', this.$sidePanel).fileList('markActive', dataId) - $(window).trigger('resize') - } else if ($tabControl.hasClass('secondary')) { - // TODO: Focus the code or rich editor here - } - - this.storeOpenTabs(); - } - - /* - * Triggered when all master tabs are closed. - */ - PagesPage.prototype.onAllTabsClosed = function() { - this.$pageTree.treeView('markActive', null) - $('[data-control=filelist]', this.$sidePanel).fileList('markActive', null) - this.setPageTitle('') - } - - /* - * Triggered when a master tab is closed. - */ - PagesPage.prototype.onTabClosed = function() { - this.updateModifiedCounter(); - this.storeOpenTabs(); - } - - /* - * Handles AJAX errors in the master tab forms. Processes the mtime mismatch condition (concurrency). - */ - PagesPage.prototype.onAjaxError = function(event, context, message, data, jqXHR) { - if (context.handler != 'onSave') - return - - if (jqXHR.responseText == 'mtime-mismatch') { - event.preventDefault() - this.handleMtimeMismatch(event.target) - } - } - - /* - * Handles successful AJAX request in the master tab forms. Updates the UI elements and resets the mtime value. - */ - PagesPage.prototype.onAjaxSuccess = function(event, context, data) { - var $form = $(event.currentTarget), - $tabPane = $form.closest('.tab-pane') - - // Update the visibilities of the commit & reset buttons - $('[data-control=commit-button]', $form).toggleClass('oc-hide hide', !data.canCommit) - $('[data-control=reset-button]', $form).toggleClass('oc-hide hide', !data.canReset) - - if (data.objectPath !== undefined) { - $('input[name=objectPath]', $form).val(data.objectPath) - $('input[name=objectMtime]', $form).val(data.objectMtime) - $('[data-control=delete-button]', $form).removeClass('oc-hide hide') - $('[data-control=preview-button]', $form).removeClass('oc-hide hide') - - if (data.pageUrl !== undefined) - $('[data-control=preview-button]', $form).attr('href', data.pageUrl) - } - - if (data.tabTitle !== undefined) { - this.$masterTabs.ocTab('updateTitle', $tabPane, data.tabTitle) - this.setPageTitle(data.tabTitle) - } - - var tabId = $('input[name=objectType]', $form).val() + '-' - + $('input[name=theme]', $form).val() + '-' - + $('input[name=objectPath]', $form).val(); - - this.$masterTabs.ocTab('updateIdentifier', $tabPane, tabId) - this.$pageTree.treeView('markActive', tabId) - $('[data-control=filelist]', this.$sidePanel).fileList('markActive', tabId) - - // Disable fancy layout on nested forms in repeater items - $('.field-repeater-item .form-tabless-fields', $tabPane).addClass('not-fancy'); - - var objectType = $('input[name=objectType]', $form).val() - if (objectType.length > 0 && - (context.handler == 'onSave' || context.handler == 'onCommit' || context.handler == 'onReset') - ) - - this.updateObjectList(objectType); - - if (context.handler == 'onSave' && (!data['X_OCTOBER_ERROR_FIELDS'] && !data['X_OCTOBER_ERROR_MESSAGE'])) - $form.trigger('unchange.oc.changeMonitor') - - // Reload the form if the server has requested it - if (data.forceReload) { - this.reloadForm($form) - } - } - - PagesPage.prototype.onBeforeSaveContent = function(e, data) { - var form = e.currentTarget, - $tabPane = $(form).closest('.tab-pane') - - this.updateContentEditorMode($tabPane, false) - } - - PagesPage.prototype.onDeletePageSingle = function(el) { - var $el = $(el); - - $el.request('onDelete', { - success: function(data) { - $.oc.pagesPage.closeTabs(data, 'page'); - $.oc.pagesPage.updateObjectList('page'); - $(this).trigger('close.oc.tab', [{force: true}]); - } - }); - } - - /* - * Updates the browser title when an object is saved. - */ - PagesPage.prototype.setPageTitle = function(title) { - $.oc.layout.setPageTitle(title.length ? (title + ' | ') : title) - } - - /* - * Updates the sidebar object list. - */ - PagesPage.prototype.updateObjectList = function(objectType) { - var $form = $('form[data-object-type='+objectType+']', this.$sidePanel), - objectList = objectType + 'List', - self = this - - $.oc.stripeLoadIndicator.show() - $form.request(objectList + '::onUpdate', { - complete: function(data) { - $('button[data-control~=delete-object], button[data-control~=delete-template]', $form).trigger('oc.triggerOn.update') - } - }).always(function(){ - $.oc.stripeLoadIndicator.hide() - }); - } - - /* - * Closes deleted page tabs in the editor area. - */ - PagesPage.prototype.closeTabs = function(data, type) { - var self = this - - $.each(data.deletedObjects, function(){ - var tabId = type + '-' + data.theme + '-' + this, - tab = self.masterTabsObj.findByIdentifier(tabId) - - $(tab).trigger('close.oc.tab', [{force: true}]) - }) - } - - /* - * Triggered when an item is clicked in the sidebar. Opens the item in the editor. - * If the item is already opened, activate its tab in the editor. - */ - PagesPage.prototype.onSidebarItemClick = function(e) { - var self = this, - $item = $(e.relatedTarget), - $form = $item.closest('form'), - theme = $('input[name=theme]', $form).val(), - data = { - type: $form.data('object-type'), - theme: theme, - path: $item.data('item-path') - }, - tabId = data.type + '-' + data.theme + '-' + data.path - - // Find if the tab is already opened - if (this.masterTabsObj.goTo(tabId)) { - return false; - } - - // Open a new tab - $.oc.stripeLoadIndicator.show() - $form - .request('onOpen', { - data: data - }).done(function(data) { - self.$masterTabs.ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon')); - }).always(function() { - $.oc.stripeLoadIndicator.hide(); - }); - - return false - } - - /* - * Triggered when the Add button is clicked on the sidebar - */ - PagesPage.prototype.onCreateObject = function(e) { - var self = this, - $button = $(e.target), - $form = $button.closest('form'), - parent = $button.data('parent') !== undefined ? $button.data('parent') : null, - type = $form.data('object-type') ? $form.data('object-type') : $form.data('template-type'), - tabId = type + Math.random() - - $.oc.stripeLoadIndicator.show() - $form.request('onCreateObject', { - data: { - type: type, - parent: parent - } - }).done(function(data){ - self.$masterTabs.ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon') + ' new-template') - $('#layout-side-panel').trigger('close.oc.sidePanel') - self.setPageTitle(data.tabTitle) - }).always(function(){ - $.oc.stripeLoadIndicator.hide() - }) - - e.stopPropagation() - - return false; - } - - /* - * Triggered when an item is clicked in the sidebar submenu - */ - PagesPage.prototype.onSidebarSubmenuItemClick = function(e) { - if ($(e.clickEvent.target).data('control') == 'create-object') { - this.onCreateObject(e.clickEvent); - } - - return false; - } - - /* - * Triggered when the Delete button is clicked on the sidebar - */ - PagesPage.prototype.onDeleteObject = function(e) { - var $el = $(e.target), - $form = $el.closest('form'), - objectType = $form.data('object-type'), - self = this - - if (!confirm($el.data('confirmation'))) - return - - $form.request('onDeleteObjects', { - data: { - type: objectType - }, - success: function(data) { - $.each(data.deleted, function(index, path){ - var tabId = objectType + '-' + data.theme + '-' + path, - tab = self.masterTabsObj.findByIdentifier(tabId) - - self.$masterTabs.ocTab('closeTab', tab, true) - }) - - if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length) - $.oc.flashMsg({text: data.error, 'class': 'error'}) - }, - complete: function() { - self.updateObjectList(objectType) - } - }) - - return false - } - - /* - * Triggered when a static page layout changes - */ - PagesPage.prototype.onLayoutChanged = function(e) { - var - self = this, - $el = $(e.target), - $form = $el.closest('form'), - $pane = $form.closest('.tab-pane'), - data = { - type: $('[name=objectType]', $form).val(), - theme: $('[name=theme]', $form).val(), - path: $('[name=objectPath]', $form).val() - }, - tab = $pane.data('tab') - - // $form.trigger('unchange.oc.changeMonitor') - $form.changeMonitor('dispose') - - $.oc.stripeLoadIndicator.show() - - $form - .request('onUpdatePageLayout', { - data: data - }) - .done(function(data){ - self.$masterTabs.ocTab('updateTab', tab, data.tabTitle, data.tab) - }) - .always(function(){ - $.oc.stripeLoadIndicator.hide() - $('form:first', $pane).changeMonitor().trigger('change') - }) - } - - /* - * Triggered when a new tab is added to the Editor - */ - PagesPage.prototype.onInitTab = function(e, data) { - if ($(e.target).attr('id') != 'pages-master-tabs') - return - - var $collapseIcon = $(''), - $panel = $('.form-tabless-fields', data.pane), - $secondaryPanel = $('.control-tabs.secondary-tabs', data.pane), - $primaryPanel = $('.control-tabs.primary-tabs', data.pane), - hasSecondaryTabs = $secondaryPanel.length > 0 - - $secondaryPanel.addClass('secondary-content-tabs') - - // Disable fancy layout on nested forms - $('.form-tabless-fields', $secondaryPanel).addClass('not-fancy'); - - $panel.append($collapseIcon) - - if (!hasSecondaryTabs) { - $primaryPanel.parent().removeClass('min-size'); - } - - $secondaryPanel.find('> .tab-content > .tab-pane').not(':has(>.form-group[data-field-name=markup],>div>div.stretch)').addClass('padded-pane'); - $secondaryPanel.find('> .layout-row > .nav-tabs > li:gt(0), > .form-tab-nav > .nav-tabs > li:gt(0)').addClass('tab-content-bg'); - - $collapseIcon.click(function(){ - $panel.toggleClass('collapsed') - - if (typeof(localStorage) !== 'undefined') - localStorage.ocPagesTablessCollapsed = $panel.hasClass('collapsed') ? 1 : 0 - - window.setTimeout(function(){ - $(window).trigger('oc.updateUi') - }, 500) - - return false - }) - - var $primaryCollapseIcon = $('') - - if ($primaryPanel.length > 0) { - $secondaryPanel.append($primaryCollapseIcon) - - $primaryCollapseIcon.click(function(){ - $primaryPanel.toggleClass('collapsed') - $secondaryPanel.toggleClass('primary-collapsed') - $(window).trigger('oc.updateUi') - if (typeof(localStorage) !== 'undefined') - localStorage.ocPagesPrimaryCollapsed = $primaryPanel.hasClass('collapsed') ? 1 : 0 - return false - }) - } else { - $secondaryPanel.addClass('primary-collapsed') - } - - if (typeof(localStorage) !== 'undefined') { - if (!$('a', data.tab).hasClass('new-template') && localStorage.ocPagesTablessCollapsed == 1) - $panel.addClass('collapsed') - - if (localStorage.ocPagesPrimaryCollapsed == 1 && hasSecondaryTabs) { - $primaryPanel.addClass('collapsed') - $secondaryPanel.addClass('primary-collapsed') - } - } - - var $form = $('form', data.pane), - self = this, - $panel = $('.form-tabless-fields', data.pane) - - this.updateContentEditorMode(data.pane, true) - - $form.on('changed.oc.changeMonitor', function() { - $panel.trigger('modified.oc.tab') - $panel.find('[data-control=commit-button]').addClass('oc-hide hide'); - $panel.find('[data-control=reset-button]').addClass('oc-hide hide'); - self.updateModifiedCounter() - }) - - $form.on('unchanged.oc.changeMonitor', function() { - $panel.trigger('unmodified.oc.tab') - self.updateModifiedCounter() - }) - } - - /* - * Triggered before a menu is saved - */ - PagesPage.prototype.onSaveMenu = function(e, data) { - var form = e.currentTarget, - items = [], - $items = $('div[data-control=treeview] > ol > li', form) - - var iterator = function(items) { - var result = [] - - $.each(items, function() { - var item = $(this).data('menu-item') - - var $subitems = $('> ol >li', this) - if ($subitems.length) - item['items'] = iterator($subitems) - - result.push(item) - }) - - return result - } - - data.options.data['itemData'] = JSON.stringify(iterator($items)) - } - - /* - * Updates the content editor to correspond to the content file extension - */ - PagesPage.prototype.updateContentEditorMode = function(pane, initialization) { - if ($('[data-toolbar-type]', pane).data('toolbar-type') !== 'content') - return - - var extension = this.getContentExtension(pane), - mode = 'html', - editor = $('[data-control=codeeditor]', pane) - - if (extension == 'html') - extension = 'htm' - - if (initialization) - $(pane).data('prev-extension', extension) - - if (extension == 'htm') { - $('[data-field-name=markup]', pane).hide() - $('[data-field-name=markup_html]', pane).show() - - if (!initialization && $(pane).data('prev-extension') != 'htm') { - var val = editor.codeEditor('getContent') - $('div[data-control=richeditor]', pane).richEditor('setContent', val) - } - } - else { - $('[data-field-name=markup]', pane).show() - $('[data-field-name=markup_html]', pane).hide() - - if (!initialization && $(pane).data('prev-extension') == 'htm') { - var val = $('div[data-control=richeditor]', pane).richEditor('getContent') - editor.codeEditor('setContent', val) - } - - var modes = $.oc.codeEditorExtensionModes - - if (modes[extension] !== undefined) - mode = modes[extension]; - - var setEditorMode = function() { - window.setTimeout(function(){ - editor.codeEditor('getEditorObject') - .getSession() - .setMode({ path: 'ace/mode/'+mode }) - }, 200) - } - - if (initialization) - editor.on('oc.codeEditorReady', setEditorMode) - else - setEditorMode() - } - - if (!initialization) - $(pane).data('prev-extension', extension) - } - - /* - * Returns the content file extension - */ - PagesPage.prototype.getContentExtension = function(form) { - var $input = $('input[name=fileName]', form), - fileName = $input.length ? $input.val() : '', - parts = fileName.split('.') - - if (parts.length >= 2) - return parts.pop().toLowerCase() - - return 'htm'; - } - - /* - * Store open tabs in a cookie - */ - PagesPage.prototype.storeOpenTabs = function () { - if (!Cookies) { - return; - } - - var openTabs = []; - document.querySelectorAll('#pages-master-tabs .tab-pane').forEach((pane) => { - var objectPath = pane.querySelector('[name=objectPath]'), - objectType = pane.querySelector('[name=objectType]'); - - if (!objectPath || !objectType) { - return; - } - - openTabs.push({ - path: objectPath.value, - type: objectType.value, - }); - }); - - Cookies.set('oc-rainlab-pages-open-tabs', JSON.stringify(openTabs), { expires: 365, path: '/' }); - } - - PagesPage.prototype.loadStoredOpenTabs = function () { - var cookieValue = Cookies.get('oc-rainlab-pages-open-tabs'); - if (!cookieValue) { - return; - } - - var openTabs = JSON.parse(cookieValue); - if (!Array.isArray(openTabs) || !openTabs.length) { - return; - } - - var self = this, - $form = $('#pages-side-panel form'); - - $.oc.stripeLoadIndicator.show(); - $form - .request('onOpenMultiple', { - data: { - openTabs: openTabs - } - }).done(function(data) { - if (!data.multiObjects || !Array.isArray(data.multiObjects)) { - return; - } - - $.each(data.multiObjects, function(index, item) { - var tabId = item.type + '-' + item.theme + '-' + item.path, - $sideForm = $('[data-object-type='+item.type+']'); - - self.$masterTabs.ocTab('addTab', item.tabTitle, item.tab, tabId, $sideForm.data('type-icon')); - }); - - self.updateModifiedCounter(); - }).always(function(){ - $.oc.stripeLoadIndicator.hide() - }); - } - - $(document).ready(function(){ - $.oc.pagesPage = new PagesPage(); - }); - -}(window.jQuery); diff --git a/assets/js/pages.editor.extension.documentcontroller.content.js b/assets/js/pages.editor.extension.documentcontroller.content.js new file mode 100644 index 00000000..b3793a27 --- /dev/null +++ b/assets/js/pages.editor.extension.documentcontroller.content.js @@ -0,0 +1,20 @@ +import { DocumentControllerBase } from '../../../../../modules/editor/assets/js/editor.extension.documentcontroller.base.js'; + +export class DocumentControllerContent extends DocumentControllerBase { + get documentType() { + return 'content'; + } + + get vueEditorComponentName() { + return 'pages-editor-component-content-editor'; + } + + beforeDocumentOpen(commandObj, nodeData) { + // The tree root ("Content") and folder nodes are not editable; content blocks are. + if (nodeData && nodeData.userData && (nodeData.userData.topLevel || nodeData.userData.isFolder)) { + return false; + } + + return true; + } +} diff --git a/assets/js/pages.editor.extension.documentcontroller.menu.js b/assets/js/pages.editor.extension.documentcontroller.menu.js new file mode 100644 index 00000000..5a6ce353 --- /dev/null +++ b/assets/js/pages.editor.extension.documentcontroller.menu.js @@ -0,0 +1,20 @@ +import { DocumentControllerBase } from '../../../../../modules/editor/assets/js/editor.extension.documentcontroller.base.js'; + +export class DocumentControllerMenu extends DocumentControllerBase { + get documentType() { + return 'menu'; + } + + get vueEditorComponentName() { + return 'pages-editor-component-menu-editor'; + } + + beforeDocumentOpen(commandObj, nodeData) { + // The tree root ("Menus") is not editable; individual menus are. + if (nodeData && nodeData.userData && nodeData.userData.topLevel) { + return false; + } + + return true; + } +} diff --git a/assets/js/pages.editor.extension.documentcontroller.staticpage.js b/assets/js/pages.editor.extension.documentcontroller.staticpage.js new file mode 100644 index 00000000..8aacabae --- /dev/null +++ b/assets/js/pages.editor.extension.documentcontroller.staticpage.js @@ -0,0 +1,109 @@ +import { DocumentControllerBase } from '../../../../../modules/editor/assets/js/editor.extension.documentcontroller.base.js'; + +export class DocumentControllerStaticPage extends DocumentControllerBase { + get documentType() { + return 'static-page'; + } + + get vueEditorComponentName() { + return 'pages-editor-component-staticpage-editor'; + } + + initListeners() { + // Persist page order/nesting when a page is dragged in the navigator. + this.on('pages:navigator-node-moved', this.onPageNodeMoved); + } + + beforeDocumentOpen(commandObj, nodeData) { + // The tree root ("Static Pages") is not an editable document; page nodes are. + if (nodeData && nodeData.userData && nodeData.userData.topLevel) { + return false; + } + + return true; + } + + // Persist a page drag. The TreeView applies the move to its own node arrays in + // completeDrop(), which only runs if we DON'T preventDefault - so let it reorder the + // tree first, then (on the next tick) serialize the updated tree and post it. + onPageNodeMoved(cmd) { + // Do not preventDefault: the tree must run completeDrop to reflect the new order. + setTimeout(() => this.persistPageStructure(), 0); + } + + async persistPageStructure() { + const structure = this.buildPageStructure(); + + // Guard: never post an empty structure (would wipe the yaml server-side). + if (!structure || !Object.keys(structure).length) { + return; + } + + $.oc.editor.application.setNavigatorReadonly(true); + try { + await $.oc.editor.application.ajaxRequest('onCommand', { + extension: this.editorNamespace, + command: 'onPageStructureUpdate', + // JSON-encode: form encoding drops the empty-object leaves that represent + // childless pages, which would otherwise post an empty structure. + documentData: { structure: JSON.stringify(structure) } + }); + await this.editorStore.refreshExtensionNavigatorNodes(this.editorNamespace, this.documentType); + } + catch (error) { + await this.editorStore.refreshExtensionNavigatorNodes(this.editorNamespace, this.documentType); + $.oc.editor.page.showAjaxErrorAlert(error, this.trans('editor::lang.common.error')); + } + finally { + $.oc.editor.application.setNavigatorReadonly(false); + } + } + + // Walk the Static Pages navigator node into a nested {path: {child: {...}}} map, + // matching the structure PageList::updateStructure() expects. + buildPageStructure() { + const sections = this.parentExtension.state.navigatorSections || []; + let pagesRoot = null; + + const findRoot = (nodes) => { + (nodes || []).forEach((node) => { + if (node.userData && node.userData.topLevel && node.uniqueKey + && node.uniqueKey.indexOf('static-page') !== -1) { + pagesRoot = node; + } + if (!pagesRoot && node.nodes) { + findRoot(node.nodes); + } + }); + }; + + sections.forEach((section) => findRoot(section.nodes)); + + const serialize = (nodes) => { + const result = {}; + (nodes || []).forEach((node) => { + const path = node.userData && node.userData.path; + if (!path) { + return; + } + result[path] = serialize(node.nodes); + }); + return result; + }; + + return pagesRoot ? serialize(pagesRoot.nodes) : {}; + } + + preprocessSettingsFields(settingsFields) { + const layouts = this.parentExtension.customData.layouts || {}; + + settingsFields.some((field) => { + if (field.property === 'layout') { + field.options = layouts; + return true; + } + }); + + return settingsFields; + } +} diff --git a/assets/js/pages.editor.extension.js b/assets/js/pages.editor.extension.js new file mode 100644 index 00000000..e38febd1 --- /dev/null +++ b/assets/js/pages.editor.extension.js @@ -0,0 +1,32 @@ +import { ExtensionBase } from '../../../../../modules/editor/assets/js/editor.extension.base.js'; +import { DocumentControllerStaticPage } from './pages.editor.extension.documentcontroller.staticpage.js'; +import { DocumentControllerMenu } from './pages.editor.extension.documentcontroller.menu.js'; +import { DocumentControllerContent } from './pages.editor.extension.documentcontroller.content.js'; + +class PagesEditorExtension extends ExtensionBase { + constructor(namespace) { + super(namespace); + } + + listDocumentControllerClasses() { + return [ + DocumentControllerStaticPage, + DocumentControllerMenu, + DocumentControllerContent + ]; + } + + onCommand(commandString, payload) { + super.onCommand(commandString, payload); + + if (commandString === 'pages:refresh-navigator') { + this.editorStore.refreshExtensionNavigatorNodes(this.editorNamespace).then(() => {}); + } + } +} + +// Register with the editor extension registry +oc.editorExtensions = oc.editorExtensions || {}; +oc.editorExtensions['pages'] = PagesEditorExtension; + +export { PagesEditorExtension }; diff --git a/assets/less/pages.less b/assets/less/pages.less deleted file mode 100644 index 82d75074..00000000 --- a/assets/less/pages.less +++ /dev/null @@ -1,209 +0,0 @@ -@import "../../../../../modules/backend/assets/less/core/boot.less"; - -.control-filelist.menu-list { - li { - > a { - position: relative; - - &:before { - position: absolute; - width: 18px; - height: 18px; - left: 17px; - top: 18px; - - content: ' '; - - background-image: url(../images/menu-icons.png); - background-position: 0 0; - background-repeat: no-repeat; - background-size: 36px auto; - } - - &:hover { - &:before { - background-position: 0 -60px; - } - } - } - - &.active > a:before { - background-position: 0 -60px; - } - } -} - -.control-filelist.content { - li > a { - position: relative; - - &:before { - position: absolute; - width: 18px; - height: 22px; - left: 18px; - top: 10px; - - content: ' '; - - background-image: url(../images/content-icons.png); - background-position: 0 0; - background-repeat: no-repeat; - background-size: 34px auto; - } - - &:hover { - &:before { - background-position: 0 -27px; - } - } - } - - li.active > a:before { - background-position: 0 -27px; - } - - li.group ul li > a:before { - left: 34px; - } -} - -.page-snippet-icon() { - position: absolute; - width: 17px; - height: 19px; - left: 18px; - top: 13px; - - content: ' '; - - background-image: url(../images/snippet-icons.png); - background-position: 0 0; - background-repeat: no-repeat; - background-size: 34px auto; -} - -.control-filelist.snippet-list { - li > a { - position: relative; - color: #808c8d; - - &:before { - .page-snippet-icon(); - - left: 18px; - top: 12px; - } - - &:hover { - &:before { - background-position: 0 -21px; - } - } - } - - li.group ul li > a:before { - left: 34px; - } -} - -@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { - .control-filelist.menu-list { - li { - > a { - &:before { - background-position: 0px -11px; - background-size: 18px auto; - } - - &:hover { - &:before { - background-position: 0px -40px; - } - } - } - - &.active > a:before { - background-position: 0px -40px; - } - } - } - - .control-filelist.content { - li { - a { - &:before { - background-position: 0px -27px; - background-size: 17px auto; - } - - &:hover { - &:before { - background-position: 0px -52px; - } - } - } - - &.active a:before { - background-position: 0px -52px; - } - } - } - - .control-filelist.snippet-list { - li { - a { - &:before { - background-position: 0px -21px; - background-size: 17px auto; - } - - &:hover { - &:before { - background-position: 0px -41px; - } - } - } - } - } -} - -.fancy-layout { - .pagesTextEditor { - border-left: 1px solid #cfd7e1 !important; - } -} - -.control-richeditor { - [data-snippet] { - &:before { - content: attr(data-name); - } - - &:after { - .page-snippet-icon(); - - left: 11px; - top: 12px; - } - - &.loading:after { - background-image:url(../images/loader-transparent.svg); - background-size: 15px 15px; - background-position: 50% 50%; - position: absolute; - width: 15px; - height: 15px; - top: 13px; - content: ' '; - .animation(spin 1s linear infinite); - } - } -} - -@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { - .control-richeditor [data-snippet]:after { - background-position: 0px -21px; - background-size: 17px auto; - } -} diff --git a/assets/less/treeview.less b/assets/less/treeview.less deleted file mode 100644 index c8bbb6ae..00000000 --- a/assets/less/treeview.less +++ /dev/null @@ -1,634 +0,0 @@ -@import "../../../../../modules/backend/assets/less/core/boot.less"; - -@color-treeview-item-bg: var(--oc-primary-bg, #ffffff); -@color-treeview-item-title: var(--oc-primary-color, #2b3e50); -@color-treeview-item-comment: #95a5a6; -@color-treeview-control: #bdc3c7; -@color-treeview-hover-bg: var(--bs-primary, #6a6cf7); -@color-treeview-hover-text: #fff; -@color-treeview-active-bg: var(--oc-selection, #5254f6); -@color-treeview-active-text: #ffffff; -@color-treeview-item-active-comment: #ffffff; -@color-treeview-light-submenu-bg: #8284f8; -@color-treeview-light-submenu-border: var(--bs-primary, #6a6cf7); -@color-treeview-cb-border: #cccccc; -@color-filelist-norecords-text: #666666; - -@font-size-base: 14px; - -.control-treeview { - margin-bottom: 40px; - - .no-data() { - padding: 18px 0; - margin: 0; - color: @color-filelist-norecords-text; - font-size: @font-size-base; - text-align: center; - font-weight: 400; - } - - ol { - margin: 0; - padding: 0; - list-style: none; - background: @color-treeview-item-bg; - - > li { - .transition(width 1s); - - > div { - font-size: @font-size-base; - font-weight: normal; - background: @color-treeview-item-bg; - border-bottom: 1px solid @tertiary-bg; - position: relative; - - > a { - color: @color-treeview-item-title; - padding: 11px 45px 10px 61px; - display: block; - line-height: 150%; - text-decoration: none; - .box-sizing(border-box); - } - - &:before { - content: ' '; - background-image: url(../images/treeview-icons.png); - background-position: 0px -28px; - background-repeat: no-repeat; - background-size: 42px auto; - - position: absolute; - width: 21px; - height: 22px; - left: 28px; - top: 15px; - } - - span.comment { - display: block; - font-weight: 400; - color: @color-treeview-item-comment; - font-size: @font-size-base - 1; - margin-top: 2px; - overflow: hidden; - text-overflow: ellipsis; - } - - > span.expand { - .hide-text(); - display: none; - position: absolute; - width: 20px; - height: 20px; - top: 19px; - left: 2px; - cursor: pointer; - color: @color-treeview-control; - .transition(transform 0.1s ease); - - &:before { - .icon(@icon-caret-right); - font-family: FontAwesome, 'octo-icon'; - line-height: 100%; - font-size: @font-size-base + 1; - - position: relative; - left: 8px; - top: 2px; - } - } - - > span.drag-handle { - .hide-text(); - .transition(opacity 0.4s); - - position: absolute; - right: 9px; - bottom: 0; - width: 18px; - height: 19px; - cursor: move; - color: @color-treeview-control; - opacity: 0; - - &:before { - .icon(@icon-bars); - font-family: FontAwesome, 'octo-icon'; - font-size: 18px; - line-height: inherit; - } - } - - span.borders { - font-size: 0; - } - - > ul.submenu { - position: absolute; - left: 20px; - bottom: -26.9px; - padding: 0; - list-style: none; - z-index: 200; - height: 27px; - display: none; - margin-left: 15px; - background-color: #7476f8; - .border-bottom-radius(8px); - .box-shadow(~"inset 0 3px 3px -3px rgba(0, 0, 0, 0.2)"); - - [data-control="create-object"] { - padding-left: 15px; - padding-right: 15px; - } - - li { - font-size: @font-size-base - 2; - - a { - display: block; - padding: 4px 3px 0 3px; - color: #fff; - text-decoration: none; - outline: none; - - i { - margin-right: 5px; - } - } - } - } - - &:hover { - > ul.submenu { - display: block; - } - } - - &:active { - > ul.submenu { - background-color: @color-treeview-active-bg; - } - } - - .checkbox { - position: absolute; - top: -2px; - right: 0; - - label { - margin-right: 0; - - &:before { - border-color: @color-treeview-cb-border; - } - } - } - - &.popover-highlight { - background-color: @color-treeview-hover-bg !important; - - &:before { - background-position: 0px -80px; - } - - > a { - color: @color-treeview-hover-text !important; - cursor: default; - } - - span { - color: @color-treeview-hover-text !important; - } - - > ul.submenu, > span.drag-handle { - display: none!important; - } - } - } - - &.dragged div, > div:hover { - background-color: @color-treeview-hover-bg !important; - - > a { - color: @color-treeview-hover-text !important; - } - - &:before { - background-position: 0px -80px; - } - - &:after { - top: 0 !important; - bottom: 0 !important; - } - - span { - color: @color-treeview-hover-text !important; - - &.drag-handle { - cursor: move; - opacity: 1; - } - - &.borders { - display: none; - } - } - } - - > div:active { - background-color: @color-treeview-active-bg !important; - - > a { - color: @color-treeview-active-text !important; - } - } - - &[data-no-drag-mode] div:hover { - span.drag-handle { - cursor: default !important; - opacity: .3 !important; - } - } - - &.dragged { - li.has-subitems, &.has-subitems { - > div:before { - background-position: 0px -52px; - } - } - - div > ul.submenu { - display: none!important; - } - } - - > ol { - padding-left: 20px; - padding-right: 20px; - } - - &[data-status=collapsed] > ol { - display: none; - } - - &.has-subitems { - > div { - &:before { - background-position: 0 0; - width: 23px; - height: 26px; - left: 26px; - } - - &:hover, &.popover-highlight { - &:before { background-position: 0px -52px; } - } - - span.expand { - display: block; - } - } - } - - &.placeholder { - position: relative; - opacity: .5; - } - - &.dragged { - position: absolute; - z-index: 2000; - opacity: .25; - - > div { - .border-radius(3px); - } - } - - &.drop-target { - > div { - background-color: #2581b8!important; - - > a { - color: @color-treeview-hover-text; - > span.comment { - color: @color-treeview-hover-text; - } - } - - &:before { - background-position: 0px -80px; - } - } - - &.has-subitems > div:before { - background-position: 0px -52px; - } - } - - &[data-status=expanded] > div > span.expand { - .transform( ~'rotate(90deg) translate(0, 0)' ); - } - - &.drag-ghost { - background-color: transparent; - box-sizing: content-box; - } - - &.active { - > div { - background: @color-treeview-active-bg; - - > a { - color: @color-treeview-item-active-comment; - - > span.comment, > span.expand { - color: @color-treeview-item-active-comment; - } - } - - > span.expand { - color: @color-treeview-item-active-comment; - } - - > span.borders { - &:before, &:after { - content: ' '; - position: absolute; - width: 100%; - height: 1px; - display: block; - left: 0; - background-color: @color-treeview-active-bg; - } - - &:before {top: -1px;} - &:after {bottom: -1px;} - } - - &:before { - background-position: 0px -80px; - } - } - - &.has-subitems > div:before { - background-position: 0px -52px; - } - } - - &.no-data { - .no-data(); - } - } - - @max-level: 10; - - .tree-view-paddings (@level) when (@level > 0) { - > li { - > ol { - > li > div { - margin-left: -20-(@max-level - @level)*20px; - margin-right: -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; - } - - > span.expand { - left: 2+(@max-level - @level + 1)*10px; - } - } - - .tree-view-paddings(@level - 1); - } - } - } - - .tree-view-paddings (@max-level); - } - - p.no-data { - .no-data(); - } - - a.menu-control { - display: block; - margin: 20px; - padding: 13px 15px; - border: dotted 2px #ebebeb; - color: #bdc3c7; - font-size: @font-size-base - 2; - font-weight: 600; - text-transform: uppercase; - border-radius: 5px; - vertical-align: middle; - - &:hover, &:focus { - text-decoration: none; - background-color: @color-treeview-hover-bg; - color: @color-treeview-hover-text; - border: none; - padding: 15px 17px; - } - - &:active { - background: @color-treeview-active-bg; - color: @color-treeview-active-text; - } - - i { - margin-right: 10px; - font-size: 14px; - } - } - - /* - * Light version of the treeview - transparent background, no bottom borders, - * smaller paddings, inline submenu - */ - &.treeview-light { - margin-bottom: 0; - margin-top: 20px; - - ol { - background-color: transparent; - > li { - > div { - background-color: transparent; - border-bottom: none; - - &:before { - top: 15px; - } - - > a { - padding-top: 10px; - padding-bottom: 10px; - } - - span.expand { - top: 19px; - } - - > span.drag-handle { - top: 0; - right: 0; - bottom: auto; - height: 100%; - width: 60px; - background: @color-treeview-light-submenu-bg; - .transition(none)!important; - - &:before { - position: absolute; - left: 50%; - top: 50%; - margin-left: -6px; - } - } - - > ul.submenu { - right: 60px; - left: auto; - bottom: auto; - top: 0; - height: 100%; - margin: 0; - background: transparent; - white-space: nowrap; - font-size: 0; - - &:before, &:after { - display: none; - } - - li { - height: 100%; - display: inline-block; - background: @color-treeview-light-submenu-bg; - border-right: 1px solid @color-treeview-light-submenu-border; - - p { - display: table; - height: 100%; - padding: 0; - margin: 0; - - a { - display: table-cell; - vertical-align: middle; - height: 100%; - padding: 0 20px; - font-size: @font-size-base - 1; - .box-sizing(border-box); - - i.control-icon { - font-size: 22px; - margin-right: 0; - } - } - } - } - } - } - } - } - } -} - -// -// Sorting guides -// - -body.dragging .control-treeview { - ol.dragging, ol.dragging ol { - background: #ccc; - padding-right: 20px; - .transition(padding 1s); - - > li { - > div { - margin-right: 0; - .transition(margin 1s); - - .custom-checkbox { - transition: opacity .5s; - opacity: 0; - } - } - } - } - - &.treeview-light { - ol.dragging, ol.dragging ol { - > li > div { - background-color: #f9f9f9; - } - } - } -} - -// -// Retina -// - -@media only screen and (min--moz-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { - .control-treeview { - ol { - > li { - > div{ - &:before { - background-position: 0px -79px; - background-size: 21px auto; - } - } - - &.has-subitems { - > div { - &:before {background-position: 0px -52px;} - &:hover, &.popover-highlight { - &:before {background-position: 0px -102px;} - } - } - - &.active > div { - &:before {background-position: 0px -102px;} - } - } - - &.dragged > div, &.dragged li > div, > div:hover, &.active > div, > div.popover-highlight { - &:before {background-position: 0px -129px;} - } - - &.dragged { - li.has-subitems, &.has-subitems { - > div:before { - background-position: 0px -102px; - } - } - } - - &.drop-target { - > div:before { - background-position: 0px -129px; - } - - &.has-subitems > div:before { - background-position: 0px -102px; - } - } - } - } - } -} diff --git a/behaviors/EditorState.php b/behaviors/EditorState.php new file mode 100644 index 00000000..2c455755 --- /dev/null +++ b/behaviors/EditorState.php @@ -0,0 +1,22 @@ +apiBag['staticPage'])) { @@ -81,6 +83,9 @@ public function injectPageTwig($page, $loader, $twig) CmsException::unmask(); } + /** + * getPageContents returns the processed markup for the static page + */ public function getPageContents($page) { if (!isset($page->apiBag['staticPage'])) { @@ -90,6 +95,9 @@ public function getPageContents($page) return $page->apiBag['staticPage']->getProcessedMarkup(); } + /** + * getPlaceholderContents returns the processed markup for a named placeholder + */ public function getPlaceholderContents($page, $placeholderName, $placeholderContents) { if (!isset($page->apiBag['staticPage'])) { @@ -99,6 +107,9 @@ public function getPlaceholderContents($page, $placeholderName, $placeholderCont return $page->apiBag['staticPage']->getProcessedPlaceholderMarkup($placeholderName, $placeholderContents); } + /** + * parseSyntaxFields converts syntax fields in the content to Twig markup + */ public function parseSyntaxFields($content) { try { diff --git a/classes/EditorExtension.php b/classes/EditorExtension.php new file mode 100644 index 00000000..2eb7202a --- /dev/null +++ b/classes/EditorExtension.php @@ -0,0 +1,446 @@ +openMenuDocument($controller); + case self::DOCUMENT_TYPE_CONTENT: + return $this->openContentDocument($controller); + default: + return $this->openPageDocument($controller); + } + } + + /** + * command_onSaveDocument dispatches to the handler for the requested document type. + */ + protected function command_onSaveDocument($controller) + { + switch (array_get((array) post('documentMetadata'), 'type')) { + case self::DOCUMENT_TYPE_MENU: + return $this->saveMenuDocument($controller); + case self::DOCUMENT_TYPE_CONTENT: + return $this->saveContentDocument($controller); + default: + return $this->savePageDocument($controller); + } + } + + /** + * command_onPageStructureUpdate persists a reordered/re-nested static page tree. + */ + protected function command_onPageStructureUpdate($controller) + { + return $this->updatePageStructure($controller); + } + + /** + * command_onDeleteDocument dispatches to the handler for the requested document type. + */ + protected function command_onDeleteDocument($controller) + { + switch (array_get((array) post('documentMetadata'), 'type')) { + case self::DOCUMENT_TYPE_MENU: + return $this->deleteMenuDocument($controller); + case self::DOCUMENT_TYPE_CONTENT: + return $this->deleteContentDocument($controller); + default: + return $this->deletePageDocument($controller); + } + } + + /** + * listJsFiles returns the client-side extension bundle. + */ + public function listJsFiles() + { + return [ + '/plugins/rainlab/pages/assets/js/pages.editor.extension.js' + ]; + } + + /** + * listVueComponents returns the Vue components required by the extension. + */ + public function listVueComponents() + { + return [ + \RainLab\Pages\VueComponents\StaticPageEditor::class, + \RainLab\Pages\VueComponents\MenuEditor::class, + \RainLab\Pages\VueComponents\ContentEditor::class + ]; + } + + /** + * getSettingsForms returns the Inspector settings forms per document type. + */ + public function getSettingsForms() + { + return [ + self::DOCUMENT_TYPE_PAGE => $this->loadLocalizedSettingsFields(\RainLab\Pages\Classes\StaticPage\Fields::class), + self::DOCUMENT_TYPE_MENU => $this->loadLocalizedSettingsFields(\RainLab\Pages\Classes\Menu\Fields::class) + ]; + } + + /** + * loadLocalizedSettingsFields loads and localizes an Inspector settings fields class. + */ + protected function loadLocalizedSettingsFields(string $fieldsClass) + { + $fields = $this->loadSettingsFields($fieldsClass); + + array_walk_recursive($fields, function (&$value, $key) { + if (is_string($value)) { + $value = trans($value); + } + }); + + return $fields; + } + + /** + * getNewDocumentsData returns the new document descriptions per document type. + */ + public function getNewDocumentsData() + { + $description = new NewDocumentDescription( + __("New page"), + [ + 'mtime' => null, + 'path' => null, + 'type' => self::DOCUMENT_TYPE_PAGE, + 'isNewDocument' => true + ] + ); + + $description->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small'); + $description->setInitialDocumentData([ + 'fileName' => '', + 'markup' => '', + 'settings' => ['title' => '', 'url' => '/'] + ]); + + $menuDescription = new NewDocumentDescription( + __("New menu"), + [ + 'mtime' => null, + 'path' => null, + 'type' => self::DOCUMENT_TYPE_MENU, + 'isNewDocument' => true + ] + ); + + $menuDescription->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small'); + $menuDescription->setInitialDocumentData([ + 'code' => '', + 'items' => [], + 'settings' => ['name' => __("New menu"), 'code' => 'new-menu'] + ]); + + $contentDescription = new NewDocumentDescription( + __("New content block"), + [ + 'mtime' => null, + 'path' => null, + 'type' => self::DOCUMENT_TYPE_CONTENT, + 'isNewDocument' => true + ] + ); + + $contentDescription->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small'); + $contentDescription->setInitialDocumentData([ + 'fileName' => '', + 'markup' => '', + 'language' => 'html' + ]); + + return [ + self::DOCUMENT_TYPE_PAGE => $description, + self::DOCUMENT_TYPE_MENU => $menuDescription, + self::DOCUMENT_TYPE_CONTENT => $contentDescription + ]; + } + + /** + * getCustomData exposes layout options to the client for the layout dropdown. + */ + public function getCustomData(): array + { + return [ + 'layouts' => $this->listLayoutOptions() + ]; + } + + /** + * listLayoutOptions returns theme layouts that support static pages. + */ + protected function listLayoutOptions(): array + { + $page = StaticPage::inTheme(Theme::getEditTheme()); + + return $page->getLayoutOptions(); + } + + /** + * getClientSideLangStrings returns language strings required by the client controller. + */ + public function getClientSideLangStrings() + { + return [ + 'backend::lang.form.save', + 'backend::lang.form.delete', + 'editor::lang.common.settings', + 'editor::lang.common.toggle_document_header', + // Plain-English strings the Vue editor components resolve via trans(). + 'Add item', + 'Content', + 'Custom Fields', + 'Menu', + 'New menu item', + 'Static page', + ]; + } + + /** + * listNavigatorSections initializes the extension's sidebar Navigator sections. + */ + public function listNavigatorSections(SectionList $sectionList, $documentType = null) + { + $theme = Theme::getEditTheme(); + + $section = $sectionList->addSection(__("Pages"), 'pages'); + $section->setHasApiMenuItems(true); + $section->setUserDataElement('uniqueKey', 'pages:root'); + + $this->addSectionMenuItems($section); + + if (!$documentType || $documentType === self::DOCUMENT_TYPE_PAGE) { + $this->addPagesNavigatorNodes($section, $theme); + } + + if (!$documentType || $documentType === self::DOCUMENT_TYPE_MENU) { + $this->addMenusNavigatorNodes($section, $theme); + } + + if (!$documentType || $documentType === self::DOCUMENT_TYPE_CONTENT) { + $this->addContentNavigatorNodes($section, $theme); + } + } + + /** + * addContentNavigatorNodes builds the list of content blocks, excluding static page content. + */ + protected function addContentNavigatorNodes($section, $theme) + { + $rootNode = $section->addNode(__("Content"), self::DOCUMENT_TYPE_CONTENT); + $rootNode + ->setDisplayMode(NodeDefinition::DISPLAY_MODE_TREE) + ->setChildKeyPrefix(self::DOCUMENT_TYPE_CONTENT.':') + ->setUserData(['topLevel' => true]); + + // Folder nodes are cached by path so files sharing a directory nest under one folder. + $folderNodes = []; + + foreach (Content::listInTheme($theme, true) as $content) { + $fileName = ltrim($content->fileName, '/'); + + // Static page content is managed by the page document type, not as content blocks. + if (starts_with($fileName, 'static-pages/') || starts_with($fileName, 'static-pages-fr/')) { + continue; + } + + $parentNode = $this->resolveContentFolderNode($rootNode, $fileName, $folderNodes); + + $node = $parentNode->addNode(basename($fileName), $fileName); + $node->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small'); + } + } + + /** + * resolveContentFolderNode returns (creating as needed) the folder node a content file nests under. + */ + protected function resolveContentFolderNode($rootNode, string $fileName, array &$folderNodes) + { + $dir = trim(dirname($fileName), './'); + if ($dir === '') { + return $rootNode; + } + + $parentNode = $rootNode; + $accumulated = ''; + + foreach (explode('/', $dir) as $segment) { + $accumulated = $accumulated === '' ? $segment : $accumulated.'/'.$segment; + + if (!isset($folderNodes[$accumulated])) { + $folder = $parentNode->addNode($segment, 'folder:'.$accumulated); + $folder + ->setDisplayMode(NodeDefinition::DISPLAY_MODE_TREE) + ->setIcon('#c0c0c0', 'backend-icon-background folder-small') + ->setUserData(['isFolder' => true]); + $folderNodes[$accumulated] = $folder; + } + + $parentNode = $folderNodes[$accumulated]; + } + + return $parentNode; + } + + /** + * addMenusNavigatorNodes builds the flat list of menus. + */ + protected function addMenusNavigatorNodes($section, $theme) + { + $rootNode = $section->addNode(__("Menus"), self::DOCUMENT_TYPE_MENU); + $rootNode + ->setChildKeyPrefix(self::DOCUMENT_TYPE_MENU.':') + ->setUserData(['topLevel' => true]); + + foreach (Menu::listInTheme($theme, true) as $menu) { + $node = $rootNode->addNode($menu->name ?: $menu->getBaseFileName(), $menu->getBaseFileName()); + $node->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small'); + } + } + + /** + * addPagesNavigatorNodes builds the hierarchical static page tree. + */ + protected function addPagesNavigatorNodes($section, $theme) + { + $rootNode = $section->addNode(__("Static Pages"), self::DOCUMENT_TYPE_PAGE); + $rootNode + ->setDisplayMode(NodeDefinition::DISPLAY_MODE_TREE) + ->setChildKeyPrefix(self::DOCUMENT_TYPE_PAGE.':') + // Pages can be dragged to reorder (sort) and re-nest (move), matching the + // original plugin's drag-tree. The reorder is persisted via command_onPageMove. + ->setDragAndDropMode([NodeDefinition::DND_SORT, NodeDefinition::DND_MOVE]) + ->setUserData(['topLevel' => true]); + + $pageList = new PageList($theme); + $this->addPageTreeNodes($pageList->getPageTree(true), $rootNode); + } + + /** + * addPageTreeNodes recursively adds page nodes and their subpages. + */ + protected function addPageTreeNodes($pages, $parentNode) + { + foreach ($pages as $pageInfo) { + $page = $pageInfo->page; + $baseName = $page->getBaseFileName(); + $title = $page->getViewBag()->property('title') ?: $baseName; + + $node = $parentNode->addNode($title, $baseName); + $node->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small'); + // path drives the drag-move handler; it identifies which page moved where. + $node->setUserData(['path' => $baseName]); + + if ($pageInfo->subpages) { + $this->addPageTreeNodes($pageInfo->subpages, $node); + } + } + } + + /** + * addSectionMenuItems adds the refresh and create menu items to a section. + */ + protected function addSectionMenuItems($section) + { + $section->addMenuItem(ItemDefinition::TYPE_TEXT, __("Refresh"), 'pages:refresh-navigator') + ->setIcon('icon-refresh'); + + $createMenuItem = new ItemDefinition(ItemDefinition::TYPE_TEXT, __("Add"), 'pages:create'); + $createMenuItem->setIcon('icon-create'); + + $createMenuItem->addItemObject( + $section->addCreateMenuItem( + ItemDefinition::TYPE_TEXT, + __("Page"), + 'pages:create-document@'.self::DOCUMENT_TYPE_PAGE + ) + ); + + $createMenuItem->addItemObject( + $section->addCreateMenuItem( + ItemDefinition::TYPE_TEXT, + __("Menu"), + 'pages:create-document@'.self::DOCUMENT_TYPE_MENU + ) + ); + + $createMenuItem->addItemObject( + $section->addCreateMenuItem( + ItemDefinition::TYPE_TEXT, + __("Content block"), + 'pages:create-document@'.self::DOCUMENT_TYPE_CONTENT + ) + ); + + if ($createMenuItem->hasItems()) { + $section->addMenuItemObject($createMenuItem); + } + } +} diff --git a/classes/Menu.php b/classes/Menu.php index 711d658c..42076386 100644 --- a/classes/Menu.php +++ b/classes/Menu.php @@ -8,20 +8,17 @@ use October\Rain\Support\Str; /** - * Represents a front-end menu. - * - * @package rainlab\pages - * @author Alexey Bobkov, Samuel Georges + * Menu represents a front-end menu. */ class Menu extends Meta { /** - * @var string The container name associated with the model, eg: pages. + * @var string dirName associated with the model, eg: pages. */ protected $dirName = 'meta/menus'; /** - * @var array The attributes that are mass assignable. + * @var array fillable attributes that are mass assignable. */ protected $fillable = [ 'content', @@ -31,29 +28,29 @@ class Menu extends Meta ]; /** - * @var array List of attribute names which are not considered "settings". + * @var array purgeable attribute names which are not considered "settings". */ protected $purgeable = [ 'code', ]; /** - * @var array The rules to be applied to the data. + * @var array rules to be applied to the data. */ public $rules = [ 'code' => 'required|regex:/^[0-9a-z\-\_]+$/i', ]; /** - * @var array The array of custom error messages. + * @var array customMessages for validation errors. */ public $customMessages = [ - 'required' => 'rainlab.pages::lang.menu.code_required', - 'regex' => 'rainlab.pages::lang.menu.invalid_code', + 'required' => 'The Code is required', + 'regex' => 'Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-', ]; /** - * Returns the menu code. + * getCodeAttribute returns the menu code * @return string */ public function getCodeAttribute() @@ -62,7 +59,7 @@ public function getCodeAttribute() } /** - * Sets the menu code. + * setCodeAttribute sets the menu code * @param string $code Specifies the file code. * @return \Cms\Classes\CmsObject Returns the object instance. */ @@ -79,7 +76,7 @@ public function setCodeAttribute($code) } /** - * Returns a default value for items attribute. + * getItemsAttribute returns a default value for the items attribute * Items are objects of the \RainLab\Pages\Classes\MenuItem class. * @return array */ @@ -94,10 +91,8 @@ public function getItemsAttribute() } /** - * Store the itemData in the items attribute - * + * setItemDataAttribute stores the itemData in the items attribute * @param array $data - * @return void */ public function setItemDataAttribute($data) { @@ -106,7 +101,7 @@ public function setItemDataAttribute($data) } /** - * Processes the content attribute to an array of menu data. + * parseContent processes the content attribute to an array of menu data * @return array|null */ protected function parseContent() @@ -121,7 +116,7 @@ protected function parseContent() } /** - * Initializes a cache item. + * initCacheItem initializes a cache item * @param array &$item The cached item array. */ public static function initCacheItem(&$item) @@ -132,9 +127,8 @@ public static function initCacheItem(&$item) } /** - * Returns the menu item references. - * This function is used on the front-end. - * @param Cms\Classes\Page $page The current page object. + * generateReferences returns the menu item references, used on the front-end + * @param \Cms\Classes\Page $page The current page object. * @return array Returns an array of the \RainLab\Pages\Classes\MenuItemReference objects. */ public function generateReferences($page) diff --git a/classes/MenuItem.php b/classes/MenuItem.php index 8791380e..61b39f71 100644 --- a/classes/MenuItem.php +++ b/classes/MenuItem.php @@ -3,34 +3,30 @@ use Event; /** - * MenuItem represents a menu item. - * This class is used in the back-end for managing the menu items. + * MenuItem represents a menu item, used in the back-end for managing the menu items. * On the front-end items are represented with the * \RainLab\Pages\Classes\MenuItemReference objects. - * - * @package rainlab\pages - * @author Alexey Bobkov, Samuel Georges */ class MenuItem { /** - * @var string Specifies the menu title + * @var string title specifies the menu title. */ public $title; /** - * @var array Specifies the item subitems + * @var array items specifies the item subitems. */ public $items = []; /** - * @var string Specifies the parent menu item. - * An object of the RainLab\Pages\Classes\MenuItem class or null. + * @var mixed parent specifies the parent menu item. + * An object of the \RainLab\Pages\Classes\MenuItem class or null. */ public $parent; /** - * @var boolean Determines whether the auto-generated menu items could have subitems. + * @var bool nesting determines whether the auto-generated menu items could have subitems. */ public $nesting; @@ -40,41 +36,44 @@ class MenuItem public $sites = false; /** - * @var string Specifies the menu item type - URL, static page, etc. + * @var string type specifies the menu item type - URL, static page, etc. */ public $type = 'url'; /** - * @var string Specifies the URL for URL-type items. + * @var string url specifies the URL for URL-type items. */ public $url; /** - * @var string Specifies the menu item code. + * @var string code specifies the menu item code. */ public $code; /** - * @var string Specifies the object identifier the item refers to. + * @var string reference specifies the object identifier the item refers to. * The identifier could be the database identifier or an object code. */ public $reference; /** - * @var boolean Indicates that generated items should replace this item. + * @var bool replace indicates that generated items should replace this item. */ public $replace; /** - * @var string Specifies the CMS page path to resolve dynamic menu items to. + * @var string cmsPage specifies the CMS page path to resolve dynamic menu items to. */ public $cmsPage; /** - * @var boolean Used by the system internally. + * @var bool exists is used by the system internally. */ public $exists = false; + /** + * @var array fillable attributes for this menu item. + */ public $fillable = [ 'title', 'nesting', @@ -88,8 +87,7 @@ class MenuItem ]; /** - * @var array Contains the view bag properties. - * This property is used by the menu editor internally. + * @var array viewBag contains the view bag properties, used by the menu editor internally. */ public $viewBag = []; @@ -99,9 +97,9 @@ class MenuItem public $attributes = []; /** - * Initializes a menu item from a data array. + * initFromArray initializes a menu item from a data array * @param array $items Specifies the menu item data. - * @return Returns an array of the MenuItem objects. + * @return array Returns an array of the MenuItem objects. */ public static function initFromArray($items) { @@ -128,7 +126,7 @@ public static function initFromArray($items) } /** - * Returns a list of registered menu item types + * getTypeOptions returns a list of registered menu item types * @return array Returns an array of registered item types */ public function getTypeOptions($keyValue = null) @@ -158,16 +156,25 @@ public function getTypeOptions($keyValue = null) return $result; } + /** + * getCmsPageOptions returns options for the CMS page dropdown + */ public function getCmsPageOptions($keyValue = null) { return []; // CMS Pages are loaded client-side } + /** + * getReferenceOptions returns options for the reference dropdown + */ public function getReferenceOptions($keyValue = null) { return []; // References are loaded client-side } + /** + * getTypeInfo returns type information resolved from menu item providers + */ public static function getTypeInfo($type) { $result = []; @@ -205,7 +212,7 @@ public static function getTypeInfo($type) } /** - * Converts the menu item data to an array + * toArray converts the menu item data to an array * @return array Returns the menu item data as array */ public function toArray() diff --git a/classes/MenuItemReference.php b/classes/MenuItemReference.php index da532cb8..35d62269 100644 --- a/classes/MenuItemReference.php +++ b/classes/MenuItemReference.php @@ -1,53 +1,49 @@ 'required', @@ -69,7 +57,7 @@ class Page extends ContentBase ]; /** - * @var array The array of custom attribute names. + * @var array attributeNames of custom attribute names */ public $attributeNames = [ 'title' => 'title', @@ -77,24 +65,7 @@ class Page extends ContentBase ]; /** - * @var array Attributes that support translation, if available. - */ - public $translatable = [ - 'code', - 'markup', - 'viewBag[title]', - 'viewBag[meta_title]', - 'viewBag[meta_description]', - ]; - - /** - * @var string Translation model used for translation, if available. - */ - public $translatableModel = 'RainLab\Translate\Classes\MLStaticPage'; - - /** - * @var string Contains the page parent file name. - * This property is used by the page editor internally. + * @var string parentFileName used by the page editor internally */ public $parentFileName; @@ -132,8 +103,8 @@ public function __construct(array $attributes = []) parent::__construct($attributes); $this->customMessages = [ - 'url.regex' => 'rainlab.pages::lang.page.invalid_url', - 'url.unique_url' => 'rainlab.pages::lang.page.url_not_unique', + 'url.regex' => __("Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/."), + 'url.unique_url' => __("This URL is already used by another page."), ]; } @@ -142,7 +113,7 @@ public function __construct(array $attributes = []) // /** - * Sets the object attributes. + * fill sets the object attributes * @param array $attributes A list of attributes to set. */ public function fill(array $attributes) @@ -160,7 +131,7 @@ public function fill(array $attributes) } /** - * Returns the attributes used for validation. + * getValidationAttributes returns the attributes used for validation * @return array */ protected function getValidationAttributes() @@ -169,8 +140,7 @@ protected function getValidationAttributes() } /** - * Validates the object properties. - * Throws a ValidationException in case of an error. + * beforeValidate validates the object properties */ public function beforeValidate() { @@ -193,7 +163,7 @@ public function beforeValidate() } /** - * Triggered before a new object is saved. + * beforeCreate is triggered before a new object is saved */ public function beforeCreate() { @@ -201,7 +171,7 @@ public function beforeCreate() } /** - * Triggered after a new object is saved. + * afterCreate is triggered after a new object is saved */ public function afterCreate() { @@ -209,7 +179,7 @@ public function afterCreate() } /** - * Adds this page to the meta index. + * appendToMeta adds this page to the meta index */ protected function appendToMeta() { @@ -245,9 +215,8 @@ protected function generateFilenameFromCode() } /** - * delete the object from the disk. - * Recursively deletes subpages. Returns a list of file names of deleted pages. - * @return array + * delete the object from the disk, recursively deleting subpages + * @return array Returns a list of file names of deleted pages. */ public function delete() { @@ -276,7 +245,7 @@ public function delete() } /** - * Removes this page to the meta index. + * removeFromMeta removes this page from the meta index */ protected function removeFromMeta() { @@ -289,7 +258,7 @@ protected function removeFromMeta() // /** - * Helper that makes a URL for a static page in the active theme. + * url makes a URL for a static page in the active theme * * Guide for the page reference: * - chairs -> content/static-pages/chairs.htm @@ -309,7 +278,7 @@ public static function url($name) } /** - * Determine the default layout for a new page + * setDefaultLayout determines the default layout for a new page * @param \RainLab\Pages\Classes\Page $parentPage */ public function setDefaultLayout($parentPage) @@ -342,7 +311,7 @@ public function setDefaultLayout($parentPage) // /** - * Returns the parent page that belongs to this one, or null. + * getParent returns the parent page that belongs to this one, or null * @return mixed */ public function getParent() @@ -362,7 +331,7 @@ public function getParent() } /** - * Returns all the child pages that belong to this one. + * getChildren returns all the child pages that belong to this one * @return array */ public function getChildren() @@ -387,8 +356,7 @@ public function getChildren() } /** - * Returns a list of layouts available in the theme. - * This method is used by the form widget. + * getLayoutOptions returns a list of layouts available in the theme * @return array Returns an array of strings. */ public function getLayoutOptions() @@ -406,14 +374,14 @@ public function getLayoutOptions() } if (!$result) { - $result[null] = Lang::get('rainlab.pages::lang.page.layouts_not_found'); + $result[null] = __("Layouts not found"); } return $result; } /** - * Looks up the Layout Cms object for this page. + * getLayoutObject looks up the Layout Cms object for this page * @return Cms\Classes\Layout */ public function getLayoutObject() @@ -470,8 +438,7 @@ public function listLayoutSyntaxFields() // /** - * listLayoutPlaceholders gets information about placeholders defined in the page layout. - * Returns an associative array of the placeholder name and codes. + * listLayoutPlaceholders gets information about placeholders defined in the page layout * @return array */ public function listLayoutPlaceholders() @@ -485,7 +452,7 @@ public function listLayoutPlaceholders() $nodes = array_merge([$bodyNode], $this->flattenTwigNode($bodyNode)); foreach ($nodes as $node) { - if (!$node instanceof \Cms\Twig\PlaceholderNode) { + if (!$node instanceof \Cms\Twig\Node\PlaceholderNode) { continue; } @@ -515,7 +482,7 @@ public function listLayoutPlaceholders() /** * flattenTwigNode recursively flattens a twig node and children * @param $node - * @return array A flat array of twig nodes + * @return array Returns a flat array of twig nodes. */ protected function flattenTwigNode($node) { @@ -533,8 +500,7 @@ protected function flattenTwigNode($node) } /** - * getPlaceholdersAttribute parses the page placeholder {% put %} tags and extracts the - * placeholder values. Returns an associative array of the placeholder names and values. + * getPlaceholdersAttribute parses the page placeholder {% put %} tags and extracts the placeholder values * @return array */ public function getPlaceholdersAttribute() @@ -548,13 +514,13 @@ public function getPlaceholdersAttribute() } $bodyNode = $this->getTwigNodeTree($this->code)->getNode('body')->getNode(0); - if ($bodyNode instanceof \Cms\Twig\PutNode) { + if ($bodyNode instanceof \Cms\Twig\Node\PutNode) { $bodyNode = [$bodyNode]; } $result = []; foreach ($bodyNode as $node) { - if (!$node instanceof \Cms\Twig\PutNode) { + if (!$node instanceof \Cms\Twig\Node\PutNode) { continue; } @@ -573,10 +539,8 @@ public function getPlaceholdersAttribute() } /** - * setPlaceholdersAttribute takes an array of placeholder data (key: code, value: content) - * and renders it as a single string of Twig markup against the "code" attribute. - * @param array $value - * @return void + * setPlaceholdersAttribute takes an array of placeholder data and renders it as Twig markup against the "code" attribute + * @param array $value */ public function setPlaceholdersAttribute($value) { @@ -666,7 +630,7 @@ public function getProcessedPlaceholderMarkup($placeholderName, $placeholderCont // /** - * Returns a cache key for this record. + * getMenuCacheKey returns a cache key for this record */ protected static function getMenuCacheKey($theme) { @@ -687,7 +651,7 @@ protected static function getMenuCacheKey($theme) } /** - * Returns whether the specified URLs are equal. + * urlsAreEqual returns whether the specified URLs are equal */ protected static function urlsAreEqual($url, $other) { @@ -695,7 +659,7 @@ protected static function urlsAreEqual($url, $other) } /** - * Clears the menu item cache + * clearMenuCache clears the menu item cache * @param \Cms\Classes\Theme $theme Specifies the current theme. */ public static function clearMenuCache($theme) @@ -704,9 +668,9 @@ public static function clearMenuCache($theme) } /** - * Handler for the pages.menuitem.getTypeInfo event. - * Returns a menu item type information. The type information is returned as array - * with the following elements: + * getMenuTypeInfo is the handler for the pages.menuitem.getTypeInfo event + * + * The type information is returned as array with the following elements: * - references - a list of the item type reference options. The options are returned in the * ["key"] => "title" format for options that don't have sub-options, and in the format * ["key"] => ["title"=>"Option title", "items"=>[...]] for options that have sub-options. Optional, @@ -738,9 +702,9 @@ public static function getMenuTypeInfo($type) } /** - * Handler for the pages.menuitem.resolveItem event. - * Returns information about a menu item. The result is an array - * with the following keys: + * resolveMenuItem is the handler for the pages.menuitem.resolveItem event + * + * The result is an array with the following keys: * - url - the menu item URL. Not required for menu item types that return all available records. * The URL should be returned relative to the website root and include the subdirectory, if any. * Use the Cms::url() helper to generate the URLs. @@ -750,8 +714,7 @@ public static function getMenuTypeInfo($type) * The items array should be added only if the $item's $nesting property value is TRUE. * @param \RainLab\Pages\Classes\MenuItem $item Specifies the menu item. * @param \Cms\Classes\Theme $theme Specifies the current theme. - * @param string $url Specifies the current page URL, normalized, in lower case - * The URL is specified relative to the website root, it includes the subdirectory name, if any. + * @param string $url Specifies the current page URL, normalized, in lower case. * @return mixed Returns an array. Returns null if the item cannot be resolved. */ public static function resolveMenuItem($item, $url, $theme) @@ -809,9 +772,7 @@ public static function resolveMenuItem($item, $url, $theme) } /** - * Handler for the backend.richeditor.getTypeInfo event. - * Returns a menu item type information. The type information is returned as array - * + * getRichEditorTypeInfo is the handler for the backend.richeditor.getTypeInfo event * @param string $type Specifies the page link type * @return array Array of available link targets keyed by URL ['https://example.com/' => 'Homepage] */ @@ -847,8 +808,7 @@ public static function getRichEditorTypeInfo($type) } /** - * Builds and caches a menu item tree. - * This method is used internally for menu items and breadcrumbs. + * buildMenuTree builds and caches a menu item tree * @param \Cms\Classes\Theme $theme Specifies the current theme. * @return array Returns an array containing the page information */ @@ -911,8 +871,7 @@ public static function buildMenuTree($theme) } /** - * Returns a list of options for the Reference drop-down menu in the - * menu item configuration form, when the Static Page item type is selected. + * listStaticPageMenuOptions returns a list of options for the Reference drop-down menu in the menu item configuration form * @return array Returns an array */ protected static function listStaticPageMenuOptions() @@ -947,11 +906,7 @@ protected static function listStaticPageMenuOptions() } /** - * Disables safe mode check for static pages. - * - * This allows developers to use placeholders in layouts even if safe mode is enabled. - * - * @return void + * checkSafeMode disables the safe mode check for static pages, allowing placeholders in layouts even if safe mode is enabled */ protected function checkSafeMode() { diff --git a/classes/PageList.php b/classes/PageList.php index c4f14a14..a88cef76 100644 --- a/classes/PageList.php +++ b/classes/PageList.php @@ -4,19 +4,22 @@ use RainLab\Pages\Classes\Page; /** - * The page list class reads and manages the static page hierarchy. - * - * @package rainlab\pages - * @author Alexey Bobkov, Samuel Georges + * PageList reads and manages the static page hierarchy. */ class PageList { + /** + * @var \Cms\Classes\Theme theme parent theme + */ protected $theme; + /** + * @var mixed configCache + */ protected static $configCache = false; /** - * Creates the page list object. + * __construct creates the page list object * @param \Cms\Classes\Theme $theme Specifies a parent theme. */ public function __construct($theme) @@ -25,8 +28,7 @@ public function __construct($theme) } /** - * Returns a list of static pages in the specified theme. - * This method is used internally by the system. + * listPages returns a list of static pages in the specified theme * @param boolean $skipCache Indicates if objects should be reloaded from the disk bypassing the cache. * @return object Returns an array of static pages. */ @@ -36,10 +38,7 @@ public function listPages($skipCache = false) } /** - * Returns a list of top-level pages with subpages. - * The method uses the theme's meta/static-pages.yaml file to build the hierarchy. The pages are returned - * in the order defined in the YAML file. The result of the method is used for building the back-end UI - * and for generating the menus. + * getPageTree returns a list of top-level pages with subpages * @param boolean $skipCache Indicates if objects should be reloaded from the disk bypassing the cache. * @return array Returns a nested array of objects: object('page': $pageObj, 'subpages'=>[...]) */ @@ -72,7 +71,7 @@ public function getPageTree($skipCache = false) } /** - * Returns the parent name of the specified page. + * getPageParent returns the parent name of the specified page * @param \Cms\Classes\Page $page Specifies a page object. * @param string Returns the parent page name. */ @@ -104,7 +103,7 @@ public function getPageParent($page) } /** - * Returns a part of the page hierarchy starting from the specified page. + * getPageSubTree returns a part of the page hierarchy starting from the specified page * @param \Cms\Classes\Page $page Specifies a page object. * @param array Returns a nested array of page names. */ @@ -137,8 +136,7 @@ public function getPageSubTree($page) } /** - * Appends page to the page hierarchy. - * The page can be added to the end of the hierarchy or as a subpage to any existing page. + * appendPage appends a page to the page hierarchy */ public function appendPage($page) { @@ -171,7 +169,7 @@ public function appendPage($page) } /** - * Removes a part of the page hierarchy starting from the specified page. + * removeSubtree removes a part of the page hierarchy starting from the specified page * @param \Cms\Classes\Page $page Specifies a page object. */ public function removeSubtree($page) @@ -198,7 +196,7 @@ public function removeSubtree($page) } /** - * Returns the parsed meta/static-pages.yaml file contents. + * getPagesConfig returns the parsed meta/static-pages.yaml file contents * @return mixed */ protected function getPagesConfig() @@ -224,7 +222,7 @@ protected function getPagesConfig() } /** - * Updates the page hierarchy structure in the theme's meta/static-pages.yaml file. + * updateStructure updates the page hierarchy structure in the theme's meta/static-pages.yaml file * @param array $structure A nested associative array representing the page structure */ public function updateStructure($structure) diff --git a/classes/Router.php b/classes/Router.php index d7b238a5..d08288c8 100644 --- a/classes/Router.php +++ b/classes/Router.php @@ -1,6 +1,5 @@ getRequestContentPath($documentData); + + $content = Content::load($this->getContentTheme(), $path); + if (!$content) { + throw new SystemException(sprintf('The content block %s was not found.', $path)); + } + + return [ + 'document' => $this->contentToDocumentArray($content), + 'metadata' => $this->contentMetadata($content) + ]; + } + + /** + * saveContentDocument creates or updates a content block. + */ + protected function saveContentDocument($controller) + { + $documentData = (array) post('documentData'); + $metadata = (array) post('documentMetadata'); + $forceSave = (bool) post('documentForceSave'); + + $theme = $this->getContentTheme(); + $path = trim((string) array_get($metadata, 'path')); + + $content = strlen($path) + ? Content::load($theme, $path) + : Content::inTheme($theme); + + if (!$content) { + throw new SystemException(sprintf('The content block %s was not found.', $path)); + } + + if ( + strlen($path) && + !$forceSave && + $content->mtime && + array_get($metadata, 'mtime') != $content->mtime + ) { + return ['mtimeMismatch' => true]; + } + + $fileName = (string) array_get($documentData, 'fileName'); + if (strlen($fileName)) { + $content->fileName = $fileName; + } + + $content->markup = (string) array_get($documentData, 'markup'); + $content->save(); + + Event::fire('cms.template.save', [$controller, $content, 'content']); + + return [ + 'metadata' => $this->contentMetadata($content) + ]; + } + + /** + * deleteContentDocument removes a content block. + */ + protected function deleteContentDocument($controller) + { + $metadata = (array) post('documentMetadata'); + $path = trim((string) array_get($metadata, 'path')); + + $content = Content::load($this->getContentTheme(), $path); + if ($content) { + $content->delete(); + Event::fire('cms.template.delete', [$controller, $content]); + } + } + + /** + * contentToDocumentArray flattens a content block into the client document shape. + */ + protected function contentToDocumentArray(Content $content): array + { + $extension = strtolower(File::extension($content->fileName)); + + return [ + 'fileName' => ltrim($content->fileName, '/'), + 'markup' => $content->markup, + 'language' => $this->contentLanguage($extension) + ]; + } + + /** + * contentLanguage maps a content file extension to an editor surface id. + * + * htm/html content blocks open in the WYSIWYG richeditor (parity with the original + * plugin); md uses the markdown editor; everything else is a plain code editor. + */ + protected function contentLanguage(string $extension): string + { + switch ($extension) { + case 'htm': + case 'html': + return 'richeditor'; + case 'md': + return 'markdown'; + case 'txt': + return 'plaintext'; + default: + return 'html'; + } + } + + /** + * contentMetadata builds the navigator/tab metadata for a content block. + */ + protected function contentMetadata(Content $content): array + { + $fileName = ltrim($content->fileName, '/'); + $navigatorPath = dirname($fileName); + if ($navigatorPath === '.') { + $navigatorPath = ''; + } + + return [ + 'mtime' => $content->mtime, + 'path' => $fileName, + 'fileName' => basename($fileName), + 'navigatorPath' => $navigatorPath, + 'uniqueKey' => $fileName, + 'type' => EditorExtension::DOCUMENT_TYPE_CONTENT + ]; + } + + /** + * getRequestContentPath extracts the requested content path from posted data. + */ + protected function getRequestContentPath($documentData): string + { + $path = is_array($documentData) + ? array_get($documentData, 'key', array_get($documentData, 'path')) + : $documentData; + + $path = trim((string) $path); + + if (!strlen($path)) { + throw new SystemException('Missing content path.'); + } + + return $path; + } + + /** + * getContentTheme returns the theme being edited. + */ + protected function getContentTheme(): Theme + { + $theme = Theme::getEditTheme(); + if (!$theme) { + throw new SystemException('The edit theme is not set.'); + } + + return $theme; + } +} diff --git a/classes/editorextension/HasMenuCrud.php b/classes/editorextension/HasMenuCrud.php new file mode 100644 index 00000000..b61b3716 --- /dev/null +++ b/classes/editorextension/HasMenuCrud.php @@ -0,0 +1,219 @@ +getRequestMenuCode($documentData); + + $menu = Menu::load($this->getMenuTheme(), $code . '.yaml'); + if (!$menu) { + throw new SystemException(sprintf('The menu %s was not found.', $code)); + } + + return [ + 'document' => $this->menuToDocumentArray($menu), + 'metadata' => $this->menuMetadata($menu) + ]; + } + + /** + * command_onSaveMenu creates or updates a menu. + */ + protected function saveMenuDocument($controller) + { + $documentData = (array) post('documentData'); + $metadata = (array) post('documentMetadata'); + $forceSave = (bool) post('documentForceSave'); + + $theme = $this->getMenuTheme(); + $code = trim((string) array_get($metadata, 'path')); + $code = preg_replace('/\.yaml$/', '', $code); + + $menu = strlen($code) + ? Menu::load($theme, $code . '.yaml') + : Menu::inTheme($theme); + + if (!$menu) { + throw new SystemException(sprintf('The menu %s was not found.', $code)); + } + + if ( + strlen($code) && + !$forceSave && + $menu->mtime && + array_get($metadata, 'mtime') != $menu->mtime + ) { + return ['mtimeMismatch' => true]; + } + + $settings = (array) array_get($documentData, 'settings', []); + $items = array_get($documentData, 'items', []); + + $menu->fill([ + 'name' => (string) array_get($settings, 'name'), + 'code' => (string) array_get($settings, 'code', $code), + 'itemData' => is_array($items) ? $this->normalizeItems($items) : [] + ]); + + $menu->save(); + + Event::fire('cms.template.save', [$controller, $menu, 'menu']); + + return [ + 'metadata' => $this->menuMetadata($menu) + ]; + } + + /** + * command_onDeleteMenu removes a menu. + */ + protected function deleteMenuDocument($controller) + { + $metadata = (array) post('documentMetadata'); + $code = preg_replace('/\.yaml$/', '', trim((string) array_get($metadata, 'path'))); + + $menu = Menu::load($this->getMenuTheme(), $code . '.yaml'); + if ($menu) { + $menu->delete(); + Event::fire('cms.template.delete', [$controller, $menu]); + } + } + + /** + * menuToDocumentArray flattens a menu into the client document shape. + */ + protected function menuToDocumentArray(Menu $menu): array + { + return [ + 'code' => $menu->getBaseFileName(), + 'items' => $this->itemsToArray($menu->items), + 'settings' => [ + 'name' => $menu->name, + 'code' => $menu->getBaseFileName() + ] + ]; + } + + /** + * normalizeItems recursively coerces boolean flags to the string format menus store. + */ + protected function normalizeItems(array $items): array + { + foreach ($items as &$item) { + foreach (['nesting', 'replace'] as $flag) { + if (array_key_exists($flag, $item)) { + $item[$flag] = (!$item[$flag] || $item[$flag] === '0') ? '0' : '1'; + } + } + + if (!empty($item['items']) && is_array($item['items'])) { + $item['items'] = $this->normalizeItems($item['items']); + } + } + + return $items; + } + + /** + * itemsToArray recursively serializes menu items into plain arrays for the client. + */ + protected function itemsToArray($items): array + { + $result = []; + $typeOptions = $this->menuItemTypeOptions(); + + foreach ($items as $item) { + $data = $item->toArray(); + + // typeLabel drives the tree-row subtitle (e.g. "Static page"), matching + // the original plugin's item list. + $data['typeLabel'] = array_get($typeOptions, $item->type, $item->type); + + if ($item->items) { + $data['items'] = $this->itemsToArray($item->items); + } + $result[] = $data; + } + + return $result; + } + + /** + * menuItemTypeOptions returns the map of menu item type => human label. + */ + protected function menuItemTypeOptions(): array + { + if ($this->menuItemTypeOptionsCache === null) { + $this->menuItemTypeOptionsCache = (new \RainLab\Pages\Classes\MenuItem)->getTypeOptions(); + } + + return $this->menuItemTypeOptionsCache; + } + + /** + * @var array|null menuItemTypeOptionsCache caches the type option map per request. + */ + protected $menuItemTypeOptionsCache = null; + + /** + * menuMetadata builds the navigator/tab metadata for a menu. + */ + protected function menuMetadata(Menu $menu): array + { + $code = $menu->getBaseFileName(); + + return [ + 'mtime' => $menu->mtime, + 'path' => $code, + 'fileName' => $code, + 'navigatorPath' => '', + 'uniqueKey' => $code, + 'type' => EditorExtension::DOCUMENT_TYPE_MENU + ]; + } + + /** + * getRequestMenuCode extracts the requested menu code from posted data. + */ + protected function getRequestMenuCode($documentData): string + { + $code = is_array($documentData) + ? array_get($documentData, 'key', array_get($documentData, 'path')) + : $documentData; + + $code = preg_replace('/\.yaml$/', '', trim((string) $code)); + + if (!strlen($code)) { + throw new SystemException('Missing menu code.'); + } + + return $code; + } + + /** + * getMenuTheme returns the theme being edited. + */ + protected function getMenuTheme(): Theme + { + $theme = Theme::getEditTheme(); + if (!$theme) { + throw new SystemException('The edit theme is not set.'); + } + + return $theme; + } +} diff --git a/classes/editorextension/HasStaticPageCrud.php b/classes/editorextension/HasStaticPageCrud.php new file mode 100644 index 00000000..88a9703c --- /dev/null +++ b/classes/editorextension/HasStaticPageCrud.php @@ -0,0 +1,323 @@ +getRequestPath($documentData); + + $page = StaticPage::load($this->getEditTheme(), $path); + if (!$page) { + throw new SystemException(sprintf('The static page %s was not found.', $path)); + } + + return [ + 'document' => $this->pageToDocumentArray($page), + 'metadata' => $this->pageMetadata($page) + ]; + } + + /** + * savePageDocument creates or updates a static page. + */ + protected function savePageDocument($controller) + { + $documentData = (array) post('documentData'); + $metadata = (array) post('documentMetadata'); + $forceSave = (bool) post('documentForceSave'); + + $theme = $this->getEditTheme(); + $path = trim((string) array_get($metadata, 'path')); + + $page = strlen($path) + ? StaticPage::load($theme, $path) + : StaticPage::inTheme($theme); + + if (!$page) { + throw new SystemException(sprintf('The static page %s was not found.', $path)); + } + + // Concurrency guard: refuse to overwrite a file changed on disk. + if ( + strlen($path) && + !$forceSave && + $page->mtime && + array_get($metadata, 'mtime') != $page->mtime + ) { + return ['mtimeMismatch' => true]; + } + + $settings = $this->cleanSyntaxFieldData((array) array_get($documentData, 'settings', [])); + + $fillData = [ + 'settings' => ['viewBag' => $settings], + 'markup' => (string) array_get($documentData, 'markup'), + ]; + + // Placeholder content is stored as {% put %} blocks, keyed by placeholder code. + $placeholders = array_get($documentData, 'placeholders'); + if (is_array($placeholders)) { + $fillData['placeholders'] = $placeholders; + } + + $page->fill($fillData); + + $page->validate(); + $page->save(); + + Event::fire('cms.template.save', [$controller, $page, 'static-page']); + + return [ + 'metadata' => $this->pageMetadata($page) + ]; + } + + /** + * cleanSyntaxFieldData strips repeater bookkeeping keys from posted viewBag data. + */ + protected function cleanSyntaxFieldData(array $data): array + { + $internalKeys = ['_index', '_group']; + + foreach ($data as $key => &$value) { + if (is_array($value)) { + foreach ($internalKeys as $internalKey) { + unset($value[$internalKey]); + } + $value = $this->cleanSyntaxFieldData($value); + } + } + + return $data; + } + + /** + * deletePageDocument removes a static page and its subpages. + */ + protected function deletePageDocument($controller) + { + $metadata = (array) post('documentMetadata'); + $path = trim((string) array_get($metadata, 'path')); + + $page = StaticPage::load($this->getEditTheme(), $path); + if ($page) { + $page->delete(); + Event::fire('cms.template.delete', [$controller, $page]); + } + } + + /** + * updatePageStructure persists a reordered/re-nested page tree to meta/static-pages.yaml. + */ + protected function updatePageStructure($controller) + { + $documentData = (array) post('documentData'); + $structure = array_get($documentData, 'structure', []); + + // The client JSON-encodes the structure (form encoding drops empty-object leaves). + if (is_string($structure)) { + $structure = json_decode($structure, true) ?: []; + } + + $theme = $this->getEditTheme(); + $pageList = new \RainLab\Pages\Classes\PageList($theme); + + // Only persist filenames that are real pages, preserving the posted hierarchy. + $valid = []; + foreach ($pageList->getPageTree(true) as $pageInfo) { + $this->collectValidPageNames($pageInfo, $valid); + } + + $clean = $this->sanitizePageStructure(is_array($structure) ? $structure : [], $valid); + + // Safety: never wipe the structure. If sanitization produced nothing while real + // pages exist, the payload was malformed — refuse rather than clear the yaml. + if (empty($clean) && !empty($valid)) { + throw new SystemException('Refusing to write an empty page structure.'); + } + + $pageList->updateStructure($clean); + + return ['success' => true]; + } + + /** + * collectValidPageNames gathers every page base filename from the page tree. + */ + protected function collectValidPageNames($pageInfo, array &$valid): void + { + $valid[$pageInfo->page->getBaseFileName()] = true; + + if (!empty($pageInfo->subpages)) { + foreach ($pageInfo->subpages as $subpage) { + $this->collectValidPageNames($subpage, $valid); + } + } + } + + /** + * sanitizePageStructure keeps only known page filenames from the posted structure. + */ + protected function sanitizePageStructure(array $structure, array $valid): array + { + $result = []; + + foreach ($structure as $fileName => $children) { + if (!is_string($fileName) || !isset($valid[$fileName])) { + continue; + } + + $result[$fileName] = is_array($children) + ? $this->sanitizePageStructure($children, $valid) + : []; + } + + return $result; + } + + /** + * pageToDocumentArray flattens a page into the client document shape. + */ + protected function pageToDocumentArray(StaticPage $page): array + { + $viewBag = (array) $page->getViewBag()->getProperties(); + + return [ + 'fileName' => ltrim($page->getBaseFileName(), '/'), + 'markup' => $page->markup, + 'placeholders' => $this->getPlaceholderData($page), + 'placeholderInfo' => $this->getPlaceholderInfo($page), + 'syntaxFieldGroups' => $this->getSyntaxFieldGroups($page), + 'settings' => $viewBag + ] + $viewBag; + } + + /** + * getSyntaxFieldGroups returns the layout syntax fields grouped into editor tabs. + * + * Each distinct field `tab` becomes one content tab (matching the original plugin); + * fields without a tab fall back to a single "Fields" group. Returns an ordered list + * of ['key' => , 'title' => ]. + */ + protected function getSyntaxFieldGroups(StaticPage $page): array + { + $groups = []; + + foreach ($page->listLayoutSyntaxFields() as $fieldCode => $fieldConfig) { + if (($fieldConfig['type'] ?? null) === 'fileupload') { + continue; + } + + $tab = trim((string) ($fieldConfig['tab'] ?? '')) ?: __("Fields"); + $key = 'syntax:'.md5($tab); + + if (!isset($groups[$key])) { + $groups[$key] = ['key' => $key, 'title' => $tab]; + } + } + + return array_values($groups); + } + + /** + * getPlaceholderData returns the current placeholder content keyed by code. + */ + protected function getPlaceholderData(StaticPage $page): array + { + $result = []; + $content = (array) $page->placeholders; + + foreach ($this->getPlaceholderInfo($page) as $code => $info) { + $result[$code] = (string) array_get($content, $code, ''); + } + + return $result; + } + + /** + * getPlaceholderInfo returns the editable placeholders defined by the page layout. + */ + protected function getPlaceholderInfo(StaticPage $page): array + { + $result = []; + + foreach ($page->listLayoutPlaceholders() as $code => $info) { + if (!empty($info['ignore'])) { + continue; + } + + $result[$code] = [ + 'title' => $info['title'], + 'type' => $info['type'] === 'text' ? 'text' : 'html' + ]; + } + + return $result; + } + + /** + * pageMetadata builds the navigator/tab metadata for a page. + */ + protected function pageMetadata(StaticPage $page): array + { + $fileName = $page->getBaseFileName(); + $navigatorPath = dirname($fileName); + if ($navigatorPath === '.') { + $navigatorPath = ''; + } + + return [ + 'mtime' => $page->mtime, + 'path' => $fileName, + 'fileName' => basename($fileName), + 'navigatorPath' => $navigatorPath, + 'uniqueKey' => $fileName, + 'type' => EditorExtension::DOCUMENT_TYPE_PAGE + ]; + } + + /** + * getRequestPath extracts the requested document path from posted data. + */ + protected function getRequestPath($documentData): string + { + // On open, the client posts documentData as { type, key }. + $path = is_array($documentData) + ? array_get($documentData, 'key', array_get($documentData, 'path')) + : $documentData; + + $path = trim((string) $path); + + if (!strlen($path)) { + throw new SystemException('Missing document path.'); + } + + return $path; + } + + /** + * getEditTheme returns the theme being edited. + */ + protected function getEditTheme(): Theme + { + $theme = Theme::getEditTheme(); + if (!$theme) { + throw new SystemException('The edit theme is not set.'); + } + + return $theme; + } +} diff --git a/classes/menu/Fields.php b/classes/menu/Fields.php new file mode 100644 index 00000000..20fff33a --- /dev/null +++ b/classes/menu/Fields.php @@ -0,0 +1,37 @@ + [ + 'title' => "Name", + 'placeholder' => "New menu", + 'type' => 'string', + 'validation' => [ + 'required' => ['message' => "The Name is required."] + ] + ], + 'code' => [ + 'title' => "Code", + 'placeholder' => "new-menu", + 'type' => 'string', + 'preset' => ['property' => "name", 'type' => 'file'], + 'validation' => [ + 'required' => ['message' => "The Code is required."], + 'regex' => [ + 'message' => "Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-", + 'pattern' => '^[0-9a-z\\-\\_]+$' + ] + ] + ] + ]; + } +} diff --git a/classes/menu/fields.yaml b/classes/menu/fields.yaml deleted file mode 100644 index 89afe68e..00000000 --- a/classes/menu/fields.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# =================================== -# Field Definitions -# =================================== - -fields: - name: - span: left - label: rainlab.pages::lang.menu.name - placeholder: rainlab.pages::lang.menu.new_name - attributes: - default-focus: 1 - - code: - span: right - placeholder: rainlab.pages::lang.menu.new_code - label: rainlab.pages::lang.menu.code - preset: - field: name - type: file - - toolbar: - type: partial - path: menu_toolbar - cssClass: collapse-visible - -tabs: - stretch: true - cssClass: master-area - paneCssClass: pane-compact - fields: - items: - stretch: true - tab: rainlab.pages::lang.menu.items - type: RainLab\Pages\FormWidgets\MenuItems diff --git a/classes/menuitem/fields.yaml b/classes/menuitem/fields.yaml index e4fa5285..91b29e7d 100644 --- a/classes/menuitem/fields.yaml +++ b/classes/menuitem/fields.yaml @@ -5,65 +5,65 @@ fields: search: - type: Rainlab\Pages\FormWidgets\MenuItemSearch + type: RainLab\Pages\FormWidgets\MenuItemSearch title: span: left - label: rainlab.pages::lang.menuitem.title + label: Title type: span: right - label: rainlab.pages::lang.menuitem.type + label: Type type: dropdown url: - label: rainlab.pages::lang.menuitem.url + label: URL reference: - label: rainlab.pages::lang.menuitem.reference + label: Reference type: dropdown cssClass: input-sidebar-control cmsPage: - label: rainlab.pages::lang.menuitem.cms_page - comment: rainlab.pages::lang.menuitem.cms_page_comment + label: CMS Page + comment: Select a page to open when the menu item is clicked. type: dropdown cssClass: input-sidebar-control nesting: - label: rainlab.pages::lang.menuitem.allow_nested_items - comment: rainlab.pages::lang.menuitem.allow_nested_items_comment + label: Allow nested items + comment: Nested items could be generated dynamically by static page and some other item types type: checkbox default: true replace: - label: rainlab.pages::lang.menuitem.replace - comment: rainlab.pages::lang.menuitem.replace_comment + label: Replace this item with its generated children + comment: Use this checkbox to push generated menu items to the same level with this item. This item itself will be hidden. type: checkbox default: true tabs: fields: viewBag[isHidden]: - label: rainlab.pages::lang.menuitem.hidden - comment: rainlab.pages::lang.menuitem.hidden_comment + label: Hidden + comment: Hide this menu item from appearing on the front-end. type: checkbox - tab: rainlab.pages::lang.menuitem.display_tab + tab: Display code: - label: rainlab.pages::lang.menuitem.code - comment: rainlab.pages::lang.menuitem.code_comment - tab: rainlab.pages::lang.menuitem.attributes_tab + label: Code + comment: Enter the menu item code if you want to access it with the API. + tab: Attributes span: auto viewBag[cssClass]: - label: rainlab.pages::lang.menuitem.css_class - comment: rainlab.pages::lang.menuitem.css_class_comment - tab: rainlab.pages::lang.menuitem.attributes_tab + label: CSS Class + comment: Enter a CSS class name to give this menu item a custom appearance. + tab: Attributes span: auto viewBag[isExternal]: - label: rainlab.pages::lang.menuitem.external_link - comment: rainlab.pages::lang.menuitem.external_link_comment + label: External link + comment: Open links for this menu item in a new window. type: checkbox - tab: rainlab.pages::lang.menuitem.attributes_tab + tab: Attributes diff --git a/classes/page/fields.yaml b/classes/page/fields.yaml deleted file mode 100644 index d25124a8..00000000 --- a/classes/page/fields.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# =================================== -# Field Definitions -# =================================== - -fields: - viewBag[title]: - span: left - label: rainlab.pages::lang.editor.title - placeholder: rainlab.pages::lang.editor.new_title - attributes: - default-focus: 1 - - viewBag[url]: - span: right - placeholder: / - label: rainlab.pages::lang.editor.url - preset: - field: viewBag[title] - type: url - prefixInput: input[data-parent-url] - - toolbar: - type: partial - path: page_toolbar - cssClass: collapse-visible - -tabs: - cssClass: master-area - fields: - viewBag[layout]: - tab: cms::lang.editor.settings - label: rainlab.pages::lang.page.layout - type: dropdown - options: getLayoutOptions - - viewBag[is_hidden]: - tab: cms::lang.editor.settings - span: left - label: rainlab.pages::lang.editor.hidden - type: checkbox - comment: rainlab.pages::lang.editor.hidden_comment - - viewBag[navigation_hidden]: - tab: cms::lang.editor.settings - span: right - label: rainlab.pages::lang.editor.navigation_hidden - type: checkbox - comment: rainlab.pages::lang.editor.navigation_hidden_comment - - viewBag[meta_title]: - tab: cms::lang.editor.meta - label: cms::lang.editor.meta_title - - viewBag[meta_description]: - tab: cms::lang.editor.meta - label: cms::lang.editor.meta_description - type: textarea - size: tiny - -secondaryTabs: - stretch: true - fields: - markup: - tab: rainlab.pages::lang.editor.content - type: richeditor - stretch: true - size: huge diff --git a/classes/staticpage/Fields.php b/classes/staticpage/Fields.php new file mode 100644 index 00000000..6b53c370 --- /dev/null +++ b/classes/staticpage/Fields.php @@ -0,0 +1,71 @@ + [ + 'title' => "Title", + 'placeholder' => "New page title", + 'type' => 'string', + 'validation' => [ + 'required' => ['message' => "The Title is required."] + ] + ], + 'url' => [ + 'title' => "URL", + 'placeholder' => "/", + 'type' => 'string', + 'preset' => ['property' => "title", 'type' => 'url'], + 'validation' => [ + 'required' => ['message' => "The URL is required."], + 'regex' => [ + 'message' => "Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/.", + 'pattern' => '^/[a-z0-9/_\\-\\.]*$' + ] + ] + ], + 'fileName' => [ + 'title' => "File Name", + 'type' => 'string', + 'preset' => ['property' => "title", 'type' => 'file'], + 'validation' => [ + 'required' => ['message' => "The File Name is required."] + ] + ], + 'layout' => [ + 'title' => "Layout", + 'type' => 'dropdown', + 'placeholder' => "Layouts not found" + ], + 'is_hidden' => [ + 'title' => "Hidden", + 'type' => 'checkbox', + 'description' => "Hidden pages are accessible only by logged-in back-end users." + ], + 'navigation_hidden' => [ + 'title' => "Hide in navigation", + 'type' => 'checkbox', + 'description' => "Check this box to hide this page from automatically generated menus and breadcrumbs." + ], + 'meta_title' => [ + 'title' => "Title", + 'type' => 'string', + 'tab' => "Meta" + ], + 'meta_description' => [ + 'title' => "Description", + 'type' => 'text', + 'size' => 'medium', + 'tab' => "Meta" + ] + ]; + } +} diff --git a/components/ChildPages.php b/components/ChildPages.php index 7efba113..9883834b 100644 --- a/components/ChildPages.php +++ b/components/ChildPages.php @@ -2,39 +2,40 @@ use Cms\Classes\ComponentBase; +/** + * ChildPages component displays a list of child pages for the current page. + */ class ChildPages extends ComponentBase { /** - * @var \RainLab\Pages\Components\StaticPage A reference to the static page component + * @var \RainLab\Pages\Components\StaticPage staticPageComponent reference */ protected $staticPageComponent; /** - * @var array Array of \RainLab\Pages\Classes\Page references to the child static page objects for the current page + * @var array childPages references to the child static page objects for the current page */ protected $childPages; /** - * @var array Child pages data - * [ - * 'url' => '', - * 'title' => '', - * 'page' => \RainLab\Pages\Classes\Page, - * 'viewBag' => array, - * 'is_hidden' => bool, - * 'navigation_hidden' => bool, - * ] + * @var array pages data for each child page */ public $pages = []; + /** + * componentDetails + */ public function componentDetails() { return [ - 'name' => 'rainlab.pages::lang.component.child_pages_name', - 'description' => 'rainlab.pages::lang.component.child_pages_description' + 'name' => 'Child pages', + 'description' => 'Displays a list of child pages for the current page' ]; } + /** + * onRun + */ public function onRun() { // Check if the staticPage component is attached to the rendering template diff --git a/components/StaticBreadcrumbs.php b/components/StaticBreadcrumbs.php index a1aa8220..0462c3d2 100644 --- a/components/StaticBreadcrumbs.php +++ b/components/StaticBreadcrumbs.php @@ -5,30 +5,31 @@ use RainLab\Pages\Classes\MenuItemReference; use RainLab\Pages\Classes\Page as StaticPageClass; use Cms\Classes\Theme; -use Request; -use Url; /** - * The static breadcrumbs component. - * - * @package rainlab\pages - * @author Alexey Bobkov, Samuel Georges + * StaticBreadcrumbs component outputs breadcrumbs for a static page. */ class StaticBreadcrumbs extends ComponentBase { /** - * @var array An array of the RainLab\Pages\Classes\MenuItemReference class. + * @var array breadcrumbs of RainLab\Pages\Classes\MenuItemReference objects */ public $breadcrumbs = []; + /** + * componentDetails + */ public function componentDetails() { return [ - 'name' => 'rainlab.pages::lang.component.static_breadcrumbs_name', - 'description' => 'rainlab.pages::lang.component.static_breadcrumbs_description' + 'name' => 'Static breadcrumbs', + 'description' => 'Outputs breadcrumbs for a static page.' ]; } + /** + * onRun + */ public function onRun() { $url = $this->getRouter()->getUrl(); diff --git a/components/StaticMenu.php b/components/StaticMenu.php index 77d3aff7..adc0b678 100644 --- a/components/StaticMenu.php +++ b/components/StaticMenu.php @@ -1,49 +1,52 @@ 'rainlab.pages::lang.component.static_menu_name', - 'description' => 'rainlab.pages::lang.component.static_menu_description' + 'name' => 'Static menu', + 'description' => 'Outputs a menu in a CMS layout.' ]; } + /** + * defineProperties + */ public function defineProperties() { return [ 'code' => [ - 'title' => 'rainlab.pages::lang.component.static_menu_code_name', - 'description' => 'rainlab.pages::lang.component.static_menu_code_description', + 'title' => 'Menu', + 'description' => 'Specify a code of the menu the component should output.', 'type' => 'dropdown' ] ]; } + /** + * getCodeOptions + */ public function getCodeOptions() { $result = []; @@ -58,11 +61,17 @@ public function getCodeOptions() return $result; } + /** + * onRun + */ public function onRun() { $this->page['menuItems'] = $this->menuItems(); } + /** + * menuItems + */ public function menuItems() { if ($this->menuItems !== null) { @@ -85,7 +94,7 @@ public function menuItems() } /** - * Counts the total menu items, including children. + * totalItems counts the total menu items, including children */ public function totalItems() { @@ -107,9 +116,7 @@ public function totalItems() } /** - * Resets the menu code and rebuilds the menu. - * @param string $code - * @return array + * resetMenu resets the menu code and rebuilds the menu */ public function resetMenu($code) { diff --git a/components/StaticPage.php b/components/StaticPage.php index c8c2c40e..49b1bbc4 100644 --- a/components/StaticPage.php +++ b/components/StaticPage.php @@ -6,67 +6,73 @@ use Cms\Models\MaintenanceSetting; /** - * The static page component. - * - * @package rainlab\pages - * @author Alexey Bobkov, Samuel Georges + * StaticPage component outputs a static page in a CMS layout. */ class StaticPage extends ComponentBase { /** - * @var \RainLab\Pages\Classes\Page A reference to the static page object + * @var \RainLab\Pages\Classes\Page pageObject reference to the static page object */ public $pageObject; /** - * @var string The static page title + * @var string title of the static page */ public $title; /** - * @var array Extra data added by syntax fields. + * @var array extraData added by syntax fields */ public $extraData = []; /** - * @var string Content cache. + * @var string contentCached */ protected $contentCached = false; + /** + * componentDetails + */ public function componentDetails() { return [ - 'name' => 'rainlab.pages::lang.component.static_page_name', - 'description' => 'rainlab.pages::lang.component.static_page_description' + 'name' => 'Static page', + 'description' => 'Outputs a static page in a CMS layout.' ]; } + /** + * defineProperties + */ public function defineProperties() { return [ 'useContent' => [ - 'title' => 'rainlab.pages::lang.component.static_page_use_content_name', - 'description' => 'rainlab.pages::lang.component.static_page_use_content_description', + 'title' => 'Use page content field', + 'description' => 'If unchecked, the content section will not appear when editing the static page. Page content will be determined solely through placeholders and variables.', 'default' => 1, 'type' => 'checkbox', 'showExternalParam' => false ], 'default' => [ - 'title' => 'rainlab.pages::lang.component.static_page_default_name', - 'description' => 'rainlab.pages::lang.component.static_page_default_description', + 'title' => 'Default layout', + 'description' => 'Defines this layout as the default for new pages', 'default' => 0, 'type' => 'checkbox', 'showExternalParam' => false ], 'childLayout' => [ - 'title' => 'rainlab.pages::lang.component.static_page_child_layout_name', - 'description' => 'rainlab.pages::lang.component.static_page_child_layout_description', + 'title' => 'Subpage layout', + 'description' => 'The layout to use as the default for any new subpages', 'type' => 'string', 'showExternalParam' => false ] ]; } + /** + * onRun + */ public function onRun() { $url = $this->getRouter()->getUrl(); @@ -88,21 +94,33 @@ public function onRun() } } + /** + * page returns the static page object + */ public function page() { return $this->pageObject; } + /** + * parent returns the parent static page + */ public function parent() { return $this->pageObject ? $this->pageObject->getParent() : null; } + /** + * children returns the child static pages + */ public function children() { return $this->pageObject ? $this->pageObject->getChildren() : null; } + /** + * content returns the processed page markup + */ public function content() { // Evaluate the content property only when it's requested in the @@ -122,8 +140,7 @@ public function content() } /** - * Find foreign view bag values and add them to - * the component and page vars. + * defineExtraData finds foreign view bag values and adds them to the component and page vars. */ protected function defineExtraData() { @@ -167,9 +184,8 @@ protected function isMaintenanceModeEnabled(): bool } /** - * Implements the getter functionality. + * __get implements the getter functionality for extra data. * @param string $name - * @return void */ public function __get($name) { @@ -181,9 +197,8 @@ public function __get($name) } /** - * Determine if an attribute exists on the object. + * __isset determines if an extra data attribute exists. * @param string $key - * @return void */ public function __isset($key) { diff --git a/composer.json b/composer.json index 2dc249b7..16ba6f6d 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,7 @@ "type": "october-plugin", "description": "Pages plugin for October CMS", "homepage": "https://octobercms.com/plugin/rainlab-pages", - "keywords": ["october", "octobercms", "pages"], - "license": "MIT", + "keywords": ["october cms", "october", "pages", "octobercms"], "authors": [ { "name": "Alexey Bobkov", @@ -18,9 +17,7 @@ } ], "require": { - "php": "^8.0.2", - "october/rain": ">=3.4", - "composer/installers": "~1.0" - }, - "minimum-stability": "dev" + "php": ">=8.2", + "october/rain": ">=4.4" + } } diff --git a/config/config.php b/config/config.php deleted file mode 100644 index eed0b81a..00000000 --- a/config/config.php +++ /dev/null @@ -1,27 +0,0 @@ - false, - - /* - |-------------------------------------------------------------------------- - | Sort Menus List By Filename - |-------------------------------------------------------------------------- - | - | You can force the menus list to be sorted by filename - | Keep empty to use the default sorting order. - | - */ - 'menus_sort_by' => false // fileName, name, mtime -]; diff --git a/controllers/Index.php b/controllers/Index.php index 27214e8e..ae07e424 100644 --- a/controllers/Index.php +++ b/controllers/Index.php @@ -1,964 +1,143 @@ theme = Theme::getEditTheme())) { - throw new ApplicationException(Lang::get('cms::lang.theme.edit.not_found')); - } - - if ($this->user) { - if ($this->user->hasAccess('rainlab.pages.manage_pages')) { - new PageList($this, 'pageList'); - $this->vars['activeWidgets'][] = 'pageList'; - } - - if ($this->user->hasAccess('rainlab.pages.manage_menus')) { - new MenuList($this, 'menuList'); - $this->vars['activeWidgets'][] = 'menuList'; - } - - if ($this->user->hasAccess('rainlab.pages.manage_content')) { - new TemplateList($this, 'contentList', function() { - return $this->getContentTemplateList(); - }); - $this->vars['activeWidgets'][] = 'contentList'; - } - } - } - catch (Exception $ex) { - $this->handleError($ex); - } - - $context = [ - 'pageList' => 'pages', - 'menuList' => 'menus', - 'contentList' => 'content', - 'snippetList' => 'snippets', - ]; - - BackendMenu::setContext('RainLab.Pages', 'pages', @$context[$this->vars['activeWidgets'][0]]); - } - - // - // Pages, menus and text blocks - // - - public function index() - { - $this->addJs('/plugins/rainlab/pages/assets/js/october.treeview.js', 'RainLab.Pages'); - $this->addJs('/plugins/rainlab/pages/assets/js/pages-page.js', 'RainLab.Pages'); - $this->addCss('/plugins/rainlab/pages/assets/css/pages.css', 'RainLab.Pages'); - $this->addCss('/plugins/rainlab/pages/assets/css/treeview.css', 'RainLab.Pages'); - - // Preload the code editor class as it could be needed - // before it loads dynamically. - $this->addJs('/modules/backend/formwidgets/codeeditor/assets/js/build-min.js', 'core'); - - $this->bodyClass = 'compact-container sidenav-responsive'; - $this->pageTitle = 'rainlab.pages::lang.plugin.name'; - $this->pageTitleTemplate = Lang::get('rainlab.pages::lang.page.template_title'); - - if (Request::ajax() && Request::input('formWidgetAlias')) { - $this->bindFormWidgetToController(); - } - } - - /** - * index_onOpen - */ - public function index_onOpen() - { - $this->validateRequestTheme(); - - $type = Request::input('type'); - $object = $this->loadObject($type, Request::input('path')); - - /* - * Extensibility - */ - Event::fire('pages.object.load', [$this, $object, $type]); - $this->fireEvent('object.load', [$object, $type]); - - return $this->pushObjectForm($type, $object); - } - - /** - * index_onOpenMultiple - */ - public function index_onOpenMultiple() - { - $result = []; - $openTabs = post('openTabs'); - - if (!is_array($openTabs)) { - return; - } - - $maxTabs = Config::get('rainlab.pages::remember_tabs_max', 5); - if ($maxTabs === false) { - return; - } - - if ($maxTabs !== 0) { - $openTabs = array_slice($openTabs, -($maxTabs)); - } - - foreach ($openTabs as $obj) { - $type = $obj['type'] ?? null; - $path = $obj['path'] ?? null; - if (!$type || !$path) { - continue; - } + BackendMenu::setContext('RainLab.Pages', 'pages'); - $object = $this->loadObject($type, $path, true); - if (!$object) { - continue; - } + $this->bodyClass = 'compact-container editor-page backend-document-layout'; + $this->pageTitle = 'Pages'; - /* - * Extensibility - */ - Event::fire('pages.object.load', [$this, $object, $type]); - $this->fireEvent('object.load', [$object, $type]); + // Re-bind the syntax fields form on every request so its nested widgets + // (repeater, mediafinder) can resolve their own AJAX handlers. + $this->bindSyntaxFieldsWidget(); - $result[] = [ - 'type' => $type, - 'path' => $path, - 'theme' => $this->theme->getDirName() - ] + $this->pushObjectForm($type, $object, null, $path); + // Re-bind the menu item form so its own widgets resolve their AJAX handlers. + if (post('bindMenuItemForm')) { + $this->makeMenuItemFormWidget(); } - - return ['multiObjects' => $result]; } /** - * onSave + * index hosts the Editor Application Vue app. */ - public function onSave() - { - $this->validateRequestTheme(); - $type = Request::input('objectType'); - - $object = $this->fillObjectFromPost($type); - $object->save(); - - /* - * Extensibility - */ - Event::fire('pages.object.save', [$this, $object, $type]); - $this->fireEvent('object.save', [$object, $type]); - - $result = $this->getUpdateResponse($object, $type); - - $successMessages = [ - 'page' => 'rainlab.pages::lang.page.saved', - 'menu' => 'rainlab.pages::lang.menu.saved', - 'content' => 'rainlab.pages::lang.content.saved', - ]; - - $successMessage = isset($successMessages[$type]) - ? $successMessages[$type] - : $successMessages['page']; - - Flash::success(Lang::get($successMessage)); - - return $result; - } - - public function onCreateObject() - { - $this->validateRequestTheme(); - - $type = Request::input('type'); - $object = $this->createObject($type); - $parent = Request::input('parent'); - $parentPage = null; - - if ($type == 'page') { - if (strlen($parent)) { - $parentPage = StaticPage::load($this->theme, $parent); - } - - $object->setDefaultLayout($parentPage); - } - - $widget = $this->makeObjectFormWidget($type, $object); - $this->vars['objectPath'] = ''; - $this->vars['canCommit'] = $this->canCommitObject($object); - $this->vars['canReset'] = $this->canResetObject($object); - - $result = [ - 'tabTitle' => $this->getTabTitle($type, $object), - 'tab' => $this->makePartial('form_page', [ - 'form' => $widget, - 'objectType' => $type, - 'objectTheme' => $this->theme->getDirName(), - 'objectMtime' => null, - 'objectParent' => $parent, - 'parentPage' => $parentPage - ]) - ]; - - return $result; - } - - public function onDelete() - { - $this->validateRequestTheme(); - - $type = Request::input('objectType'); - - $deletedObjects = $this->loadObject($type, trim(Request::input('objectPath')))->delete(); - - $result = [ - 'deletedObjects' => $deletedObjects, - 'theme' => $this->theme->getDirName() - ]; - - return $result; - } - - public function onDeleteObjects() - { - $this->validateRequestTheme(); - - $type = Request::input('type'); - $objects = Request::input('object'); - - if (!$objects) { - $objects = Request::input('template'); - } - - $error = null; - $deleted = []; - - try { - foreach ($objects as $path => $selected) { - if (!$selected) { - continue; - } - $object = $this->loadObject($type, $path, true); - if (!$object) { - continue; - } - - $deletedObjects = $object->delete(); - if (is_array($deletedObjects)) { - $deleted = array_merge($deleted, $deletedObjects); - } - else { - $deleted[] = $path; - } - } - } - catch (Exception $ex) { - $error = $ex->getMessage(); - } - - return [ - 'deleted' => $deleted, - 'error' => $error, - 'theme' => Request::input('theme') - ]; - } - - public function onOpenConcurrencyResolveForm() - { - return $this->makePartial('concurrency_resolve_form'); - } - - public function onGetMenuItemTypeInfo() - { - $type = Request::input('type'); - - return [ - 'menuItemTypeInfo' => MenuItem::getTypeInfo($type) - ]; - } - - public function onUpdatePageLayout() - { - $this->validateRequestTheme(); - - $type = Request::input('objectType'); - - $object = $this->fillObjectFromPost($type); - - return $this->pushObjectForm($type, $object, Request::input('formWidgetAlias')); - } - - public function onMenuItemReferenceSearch() - { - $alias = Request::input('alias'); - - $widget = $this->makeFormWidget( - 'Rainlab\Pages\FormWidgets\MenuItemSearch', - [], - ['alias' => $alias] - ); - - return $widget->onSearch(); - } - - /** - * onCommit commits the DB changes of a object to the filesystem - * @return array $response - */ - public function onCommit() - { - $this->validateRequestTheme(); - $type = Request::input('objectType'); - $object = $this->loadObject($type, trim(Request::input('objectPath'))); - - if ($this->canCommitObject($object)) { - $datasource = $this->getThemeDatasource(); - $datasource->updateModelAtIndex(1, $object); - $datasource->forceDeleteModelAtIndex(0, $object); - Flash::success(Lang::get('cms::lang.editor.commit_success', ['type' => $type])); - } - - return array_merge($this->getUpdateResponse($object, $type), ['forceReload' => true]); - } - - /** - * Resets a object to the version on the filesystem - * - * @return array $response - */ - public function onReset() - { - $this->validateRequestTheme(); - $type = Request::input('objectType'); - $object = $this->loadObject($type, trim(Request::input('objectPath'))); - - if ($this->canResetObject($object)) { - $datasource = $this->getThemeDatasource(); - $datasource->forceDeleteModelAtIndex(0, $object); - Flash::success(Lang::get('cms::lang.editor.reset_success', ['type' => $type])); - } - - return array_merge($this->getUpdateResponse($object, $type), ['forceReload' => true]); - } - - // - // Methods for internal use - // - - /** - * Get the response to return in an AJAX request that updates an object - * - * @param CmsObject $object The object that has been affected - * @param string $type The type of object being affected - * @return array $result; - */ - protected function getUpdateResponse(CmsObject $object, string $type) - { - $result = [ - 'objectPath' => $type != 'content' ? $object->getBaseFileName() : $object->fileName, - 'objectMtime' => $object->mtime, - 'tabTitle' => $this->getTabTitle($type, $object) - ]; - - if ($type == 'page') { - $result['pageUrl'] = $this->getPreviewPageUrl($object); - PagesPlugin::clearCache(); - } - - $result['canCommit'] = $this->canCommitObject($object); - $result['canReset'] = $this->canResetObject($object); - - return $result; - } - - /** - * Get the active theme's datasource - */ - protected function getThemeDatasource() - { - return $this->theme->getDatasource(); - } - - /** - * Check to see if the provided object can be committed - * Only available in debug mode, the DB layer must be enabled, and the object must exist in the database - * - * @param CmsObject $object - * @return boolean - */ - protected function canCommitObject(CmsObject $object) - { - $result = false; - - if ( - Config::get('app.debug', false) && - $this->theme->secondLayerEnabled() && - $this->getThemeDatasource()->hasModelAtIndex(1, $object) - ) { - $result = true; - } - - return $result; - } - - /** - * Check to see if the provided object can be reset - * Only available when the DB layer is enabled and the object exists in both the DB & Filesystem - * - * @param CmsObject $object - * @return boolean - */ - protected function canResetObject(CmsObject $object) - { - $result = false; - - if ($this->theme->secondLayerEnabled()) { - $datasource = $this->getThemeDatasource(); - $result = $datasource->hasModelAtIndex(0, $object) && - $datasource->hasModelAtIndex(1, $object); - } - - return $result; - } - - /** - * validateRequestTheme - */ - protected function validateRequestTheme() - { - if ($this->theme->getDirName() != Request::input('theme')) { - throw new ApplicationException(trans('cms::lang.theme.edit.not_match')); - } - } - - /** - * loadObject - */ - protected function loadObject($type, $path, $ignoreNotFound = false) - { - $class = $this->resolveTypeClassName($type); - - if (!($object = call_user_func(array($class, 'load'), $this->theme, $path))) { - if (!$ignoreNotFound) { - throw new ApplicationException(trans('rainlab.pages::lang.object.not_found')); - } - - return null; - } - - return $object; - } - - /** - * createObject - */ - protected function createObject($type) - { - $class = $this->resolveTypeClassName($type); - - if (!($object = $class::inTheme($this->theme))) { - throw new ApplicationException(trans('rainlab.pages::lang.object.not_found')); - } - - return $object; - } - - /** - * resolveTypeClassName - */ - protected function resolveTypeClassName($type) - { - $types = [ - 'page' => \RainLab\Pages\Classes\Page::class, - 'menu' => \RainLab\Pages\Classes\Menu::class, - 'content' => \RainLab\Pages\Classes\Content::class - ]; - - if (!array_key_exists($type, $types)) { - throw new ApplicationException(Lang::get('rainlab.pages::lang.object.invalid_type') . ' - type - ' . $type); - } - - $allowed = false; - if ($type === 'content') { - $allowed = $this->user->hasAccess('rainlab.pages.manage_content'); - } - else { - $allowed = $this->user->hasAccess("rainlab.pages.manage_{$type}s"); - } - - if (!$allowed) { - throw new ApplicationException(Lang::get('rainlab.pages::lang.object.unauthorized_type', ['type' => $type])); - } - - return $types[$type]; - } - - protected function makeObjectFormWidget($type, $object, $alias = null) + public function index() { - $formConfigs = [ - 'page' => '~/plugins/rainlab/pages/classes/page/fields.yaml', - 'menu' => '~/plugins/rainlab/pages/classes/menu/fields.yaml', - 'content' => '~/plugins/rainlab/pages/classes/content/fields.yaml' - ]; - - if (!array_key_exists($type, $formConfigs)) { - throw new ApplicationException(Lang::get('rainlab.pages::lang.object.not_found')); - } + $this->addCss('/modules/editor/assets/css/editor.css'); + $this->addCss('/plugins/rainlab/pages/assets/css/editor.css'); + $this->addJs('/modules/editor/assets/js/editor.page.js', ['type' => 'module']); - $widgetConfig = $this->makeConfig($formConfigs[$type]); - $widgetConfig->model = $object; - $widgetConfig->alias = $alias ?: 'form' . studly_case($type) . md5($object->exists ? $object->getFileName() : uniqid()); - $widgetConfig->context = !$object->exists ? 'create' : 'update'; - $widgetConfig->useTranslatable = false; + $this->registerVueComponent(\Backend\VueComponents\Document::class); + $this->registerVueComponent(\Backend\VueComponents\Tabs::class); + $this->registerVueComponent(\Backend\VueComponents\TreeView::class); + $this->registerVueComponent(\Backend\VueComponents\Splitter::class); + $this->registerVueComponent(\Backend\VueComponents\Modal::class); + $this->registerVueComponent(\Backend\VueComponents\Inspector::class); + $this->registerVueComponent(\Backend\VueComponents\Uploader::class); - $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); + $this->registerVueComponent(\Editor\VueComponents\EditorConflictResolver::class); + $this->registerVueComponent(\Editor\VueComponents\Application::class); - if ($type == 'page') { - $widget->bindEvent('form.extendFieldsBefore', function() use ($widget, $object) { - $this->checkContentField($widget, $object); - $this->addPagePlaceholders($widget, $object); - $this->addPageSyntaxFields($widget, $object); - }); + // Register only the assets of extensions in the Pages context, keeping the page + // scoped to it. + $manager = ExtensionManager::instance(); + foreach ($manager->listJsFiles(EditorExtension::CONTEXT) as $jsFile) { + $this->addJs($jsFile, ['type' => 'module']); } - - return $widget; - } - - protected function checkContentField($formWidget, $page) - { - if (!($layout = $page->getLayoutObject())) { - return; - } - - $component = $layout->getComponent('staticPage'); - - if (!$component) { - return; + foreach ($manager->listVueComponents(EditorExtension::CONTEXT) as $componentClass) { + $this->registerVueComponent($componentClass); } - if (!$component->property('useContent', true)) { - unset($formWidget->secondaryTabs['fields']['markup']); - } + $this->vars['customLogo'] = BrandSetting::getLogo(); + $this->vars['initialState'] = $this->makeInitialState([]); } /** - * addPageSyntaxFields adds syntax defined fields to the form + * index_onCommand routes a client command to an extension in the Pages context. */ - protected function addPageSyntaxFields($formWidget, $page) - { - $fields = $page->listLayoutSyntaxFields(); - - foreach ($fields as $fieldCode => $fieldConfig) { - if ($fieldConfig['type'] === 'fileupload') { - continue; - } - - if (in_array($fieldConfig['type'], ['repeater', 'nestedform'])) { - if (empty($fieldConfig['form']) || !is_string($fieldConfig['form'])) { - $repeaterFields = array_get($fieldConfig, 'fields', []); - $fieldConfig['form']['fields'] = $repeaterFields; - unset($fieldConfig['fields']); - } - } - - /* - * Custom fields placement - */ - $placement = !empty($fieldConfig['placement']) ? $fieldConfig['placement'] : null; - - switch ($placement) { - case 'primary': - $formWidget->tabs['fields']['viewBag[' . $fieldCode . ']'] = $fieldConfig; - break; - - default: - $fieldConfig['cssClass'] = 'secondary-tab ' . array_get($fieldConfig, 'cssClass', ''); - $formWidget->secondaryTabs['fields']['viewBag[' . $fieldCode . ']'] = $fieldConfig; - break; - } - - /* - * Translation support - */ - $translatableTypes = ['text', 'textarea', 'richeditor', 'repeater', 'markdown', 'mediafinder', 'nestedform']; - if (in_array($fieldConfig['type'], $translatableTypes) && array_get($fieldConfig, 'translatable', true)) { - $page->translatable[] = 'viewBag['.$fieldCode.']'; - } - } - } - - protected function addPagePlaceholders($formWidget, $page) - { - $placeholders = $page->listLayoutPlaceholders(); - - foreach ($placeholders as $placeholderCode => $info) { - if ($info['ignore']) { - continue; - } - - $placeholderTitle = $info['title']; - $fieldConfig = [ - 'tab' => $placeholderTitle, - 'stretch' => '1', - 'size' => 'huge' - ]; - - if ($info['type'] != 'text') { - $fieldConfig['type'] = 'richeditor'; - } - else { - $fieldConfig['type'] = 'codeeditor'; - $fieldConfig['language'] = 'text'; - $fieldConfig['theme'] = 'chrome'; - $fieldConfig['showGutter'] = false; - $fieldConfig['highlightActiveLine'] = false; - $fieldConfig['cssClass'] = 'pagesTextEditor'; - $fieldConfig['showInvisibles'] = false; - $fieldConfig['fontSize'] = 13; - $fieldConfig['margin'] = '20'; - } - - $formWidget->secondaryTabs['fields']['placeholders['.$placeholderCode.']'] = $fieldConfig; - - /* - * Translation support - */ - $page->translatable[] = 'placeholders['.$placeholderCode.']'; - } - } - - protected function getTabTitle($type, $object) + public function index_onCommand() { - if ($type == 'page') { - $viewBag = $object->getViewBag(); - $result = $viewBag ? $viewBag->property('title') : false; - if (!$result) { - $result = trans('rainlab.pages::lang.page.new'); - } - - return $result; + $namespace = post('extension'); + if (!is_scalar($namespace) || !strlen($namespace)) { + throw new SystemException('Missing extension name'); } - elseif ($type == 'menu') { - $result = $object->name; - if (!strlen($result)) { - $result = trans('rainlab.pages::lang.menu.new'); - } - return $result; + // Only run commands for extensions belonging to the Pages context, keeping this + // page isolated from the global editor extensions. + $extension = ExtensionManager::instance()->getExtensionByNamespace($namespace); + if (!$extension->hasEditorContext(EditorExtension::CONTEXT)) { + throw new SystemException('Unsupported extension: '.$namespace); } - elseif ($type == 'content') { - $result = in_array($type, ['asset', 'content']) - ? $object->getFileName() - : $object->getBaseFileName(); - - if (!$result) { - $result = trans('cms::lang.'.$type.'.new'); - } - return $result; + $command = post('command'); + if (!is_scalar($command) || !strlen($command)) { + throw new SystemException('Missing command'); } - return $object->getFileName(); + return ExtensionManager::instance()->runCommand($namespace, $command, $this); } /** - * fillObjectFromPost + * onListExtensionNavigatorSections refreshes the navigator sections. */ - protected function fillObjectFromPost($type) + public function onListExtensionNavigatorSections() { - $objectPath = trim(Request::input('objectPath')); - $object = $objectPath ? $this->loadObject($type, $objectPath) : $this->createObject($type); - - // Set page layout super early because it cascades to other elements - if ($type === 'page' && ($layout = post('viewBag[layout]'))) { - $object->getViewBag()->setProperty('layout', $layout); + $namespace = post('extension'); + if (!is_scalar($namespace) || !strlen($namespace)) { + throw new SystemException('Missing extension namespace'); } - $formWidget = $this->makeObjectFormWidget($type, $object, Request::input('formWidgetAlias')); - - $saveData = $formWidget->getSaveData(); - $postData = post(); - $objectData = []; - - if ($viewBag = array_get($saveData, 'viewBag')) { - $objectData['settings'] = ['viewBag' => $viewBag]; + $documentType = post('documentType'); + if ($documentType && !is_scalar($documentType)) { + throw new SystemException('Invalid document type'); } - $fields = ['markup', 'code', 'fileName', 'content', 'itemData', 'name']; - - if ($type != 'menu' && $type != 'content') { - $object->parentFileName = Request::input('parentFileName'); - } - - foreach ($fields as $field) { - if (array_key_exists($field, $saveData)) { - $objectData[$field] = $saveData[$field]; - } - elseif (array_key_exists($field, $postData)) { - $objectData[$field] = $postData[$field]; - } - } - - if ($type == 'page') { - $placeholders = array_get($saveData, 'placeholders'); - if (is_array($placeholders) && Config::get('system.convert_line_endings', false) === true) { - $placeholders = array_map([$this, 'convertLineEndings'], $placeholders); - } - - $objectData['placeholders'] = $placeholders; - } - - if ($type == 'content') { - $fileName = $objectData['fileName']; - - if (dirname($fileName) == 'static-pages') { - throw new ApplicationException(trans('rainlab.pages::lang.content.cant_save_to_dir')); - } - - $extension = pathinfo($fileName, PATHINFO_EXTENSION); - - if ($extension === 'htm' || $extension === 'html' || !strlen($extension)) { - $objectData['markup'] = array_get($saveData, 'markup_html'); - } + $extension = ExtensionManager::instance()->getExtensionByNamespace($namespace); + if (!$extension->hasEditorContext(EditorExtension::CONTEXT)) { + throw new SystemException('Unsupported extension namespace'); } - if ($type == 'menu') { - // If no item data is sent through POST, this means the menu is empty - if (!isset($objectData['itemData'])) { - $objectData['itemData'] = []; - } else { - $objectData['itemData'] = json_decode($objectData['itemData'], true); - if (json_last_error() !== JSON_ERROR_NONE || !is_array($objectData['itemData'])) { - $objectData['itemData'] = []; - } - } - } - - if (!empty($objectData['markup']) && Config::get('system.convert_line_endings', false) === true) { - $objectData['markup'] = $this->convertLineEndings($objectData['markup']); - } - - /* - * Extensibility - */ - Event::fire('pages.object.fillObject', [$this, $object, &$objectData, $type]); - $this->fireEvent('object.fillObject', [$object, &$objectData, $type]); - - if (!Request::input('objectForceSave') && $object->mtime) { - if (Request::input('objectMtime') != $object->mtime) { - throw new ApplicationException('mtime-mismatch'); - } - } - - $object->fill($objectData); - - /* - * Rehydrate the object viewBag array property where values are sourced. - */ - if ($object instanceof CmsCompoundObject && is_array($viewBag)) { - $object->viewBag = $viewBag + $object->viewBag; - } - - return $object; - } - - /** - * pushObjectForm - */ - protected function pushObjectForm($type, $object, $alias = null, $path = null) - { - $widget = $this->makeObjectFormWidget($type, $object, $alias); - - $this->vars['canCommit'] = $this->canCommitObject($object); - $this->vars['canReset'] = $this->canResetObject($object); - $this->vars['objectPath'] = Request::input('path', $path); - $this->vars['lastModified'] = DateTime::makeCarbon($object->mtime); - - if ($type == 'page') { - $this->vars['pageUrl'] = $this->getPreviewPageUrl($object); - } + $namespace = $extension->getNamespaceNormalized(); return [ - 'tabTitle' => $this->getTabTitle($type, $object), - 'tab' => $this->makePartial('form_page', [ - 'form' => $widget, - 'objectType' => $type, - 'objectTheme' => $this->theme->getDirName(), - 'objectMtime' => $object->mtime, - 'objectParent' => Request::input('parentFileName') - ]) + 'sections' => $this->listExtensionNavigatorSections($extension, $namespace, $documentType) ]; } - - /** - * getPreviewPageUrl - */ - protected function getPreviewPageUrl($object) - { - $pageUrl = $object->getViewBag()->property('url'); - - // Support for October CMS 3.0 and below - if (!class_exists('Site')) { - return Url::to($pageUrl); - } - - /** - * Hook the site picker to determine preview - * @see \Cms\Components\SitePicker - */ - $eventPattern = Event::fire('cms.sitePicker.overridePattern', [ - $object, - $pageUrl, - Site::getEditSite(), - Site::getEditSite() - ], true); - - if ($eventPattern) { - $pageUrl = $eventPattern; - } - - return Cms::fullUrl($pageUrl); - } - - /** - * bindFormWidgetToController - */ - protected function bindFormWidgetToController() - { - $alias = Request::input('formWidgetAlias'); - $type = Request::input('objectType'); - $objectPath = trim(Request::input('objectPath')); - - if (!$objectPath) { - $object = $this->createObject($type); - } - else { - $object = $this->loadObject($type, $objectPath); - } - - // Set page layout super early because it cascades to other elements - if ($type === 'page' && ($layout = post('viewBag[layout]'))) { - $object->getViewBag()->setProperty('layout', $layout); - } - - $widget = $this->makeObjectFormWidget($type, $object, $alias); - $widget->bindToController(); - } - - /** - * Replaces Windows style (/r/n) line endings with unix style (/n) - * line endings. - * @param string $markup The markup to convert to unix style endings - * @return string - */ - protected function convertLineEndings($markup) - { - $markup = str_replace("\r\n", "\n", $markup); - $markup = str_replace("\r", "\n", $markup); - - return $markup; - } - - /** - * Returns a list of content files - * @return \October\Rain\Database\Collection - */ - protected function getContentTemplateList() - { - $templates = Content::listInTheme($this->theme, true); - - /** - * @event pages.content.templateList - * Provides opportunity to filter the items returned to the ContentList widget used by the RainLab.Pages plugin in the backend. - * - * >**NOTE**: Recommended to just use cms.object.listInTheme instead - * - * Parameter provided is `$templates` (a collection of the Content CmsObjects being returned). - * > Note: The `$templates` parameter provided is an object reference to a CmsObjectCollection, to make changes you must use object modifying methods. - * - * Example usage (only shows allowed content files): - * - * \Event::listen('pages.content.templateList', function ($templates) { - * foreach ($templates as $index = $content) { - * if (!in_array($content->fileName, $allowedContent)) { - * $templates->forget($index); - * } - * } - * }); - * - * Or: - * - * \RainLab\Pages\Controller\Index::extend(function ($controller) { - * $controller->bindEvent('content.templateList', function ($templates) { - * foreach ($templates as $index = $content) { - * if (!in_array($content->fileName, $allowedContent)) { - * $templates->forget($index); - * } - * } - * }); - * }); - * } - */ - if ( - ($event = $this->fireEvent('content.templateList', [$templates], true)) || - ($event = Event::fire('pages.content.templateList', [$this, $templates], true)) - ) { - return $event; - } - - return $templates; - } } diff --git a/controllers/index/HasMenuItemForm.php b/controllers/index/HasMenuItemForm.php new file mode 100644 index 00000000..20e8d6c4 --- /dev/null +++ b/controllers/index/HasMenuItemForm.php @@ -0,0 +1,76 @@ +menuItemFormWidget !== null) { + return $this->menuItemFormWidget; + } + + $menuItem = new MenuItem; + + $config = $this->makeConfig('~/plugins/rainlab/pages/classes/menuitem/fields.yaml'); + $config->model = $menuItem; + $config->alias = 'menuItemForm'; + $config->arrayName = 'menuItem'; + + $widget = $this->makeWidget(\Backend\Widgets\Form::class, $config); + $widget->bindToController(); + + return $this->menuItemFormWidget = $widget; + } + + /** + * onLoadMenuItemForm renders the per-item Form widget over AJAX. + */ + public function onLoadMenuItemForm() + { + $widget = $this->makeMenuItemFormWidget(); + + return [ + '#pagesMenuItemForm' => $widget->render(['useContainer' => false]) + ]; + } + + /** + * onGetMenuItemTypeInfo returns type info (references, cmsPages, nesting) for a menu item type. + */ + public function onGetMenuItemTypeInfo() + { + $type = trim((string) post('type')); + + return [ + 'menuItemTypeInfo' => MenuItem::getTypeInfo($type) + ]; + } + + /** + * onMenuItemReferenceSearch returns matching references for the reference search field. + */ + public function onMenuItemReferenceSearch() + { + $alias = trim((string) post('alias')); + + $widget = new MenuItemSearch($this, ['alias' => $alias]); + + return $widget->onSearch(); + } +} diff --git a/controllers/index/HasSyntaxFields.php b/controllers/index/HasSyntaxFields.php new file mode 100644 index 00000000..9eb8c170 --- /dev/null +++ b/controllers/index/HasSyntaxFields.php @@ -0,0 +1,133 @@ +syntaxFieldsWidget !== null) { + return $this->syntaxFieldsWidget; + } + + $theme = Theme::getEditTheme(); + $page = StaticPage::load($theme, $path); + if (!$page) { + return null; + } + + $config = $this->makeConfig(['fields' => []]); + $config->model = $page; + $config->alias = 'pagesSyntaxForm'; + $config->arrayName = 'syntaxFields'; + $config->context = $page->exists ? 'update' : 'create'; + + $widget = $this->makeWidget(\Backend\Widgets\Form::class, $config); + + $widget->bindEvent('form.extendFieldsBefore', function () use ($widget, $page, $tab) { + $this->addPageSyntaxFields($widget, $page, $tab); + }); + + $widget->bindToController(); + + return $this->syntaxFieldsWidget = $widget; + } + + /** + * addPageSyntaxFields injects the layout syntax fields into the form widget. + * + * When $tab is provided, only fields whose config tab matches are added (fields without + * a tab belong to the "Fields" group). + */ + protected function addPageSyntaxFields($formWidget, $page, $tab = null) + { + $fields = $page->listLayoutSyntaxFields(); + + foreach ($fields as $fieldCode => $fieldConfig) { + if ($fieldConfig['type'] === 'fileupload') { + continue; + } + + if ($tab !== null) { + $fieldTab = trim((string) ($fieldConfig['tab'] ?? '')) ?: __("Fields"); + if ($fieldTab !== $tab) { + continue; + } + } + + if (in_array($fieldConfig['type'], ['repeater', 'nestedform'])) { + if (empty($fieldConfig['form']) || !is_string($fieldConfig['form'])) { + $repeaterFields = array_get($fieldConfig, 'fields', []); + $fieldConfig['form']['fields'] = $repeaterFields; + unset($fieldConfig['fields']); + } + } + + // Drop the tab hint so the island doesn't render its own tab strip; the editor + // provides the tab. + unset($fieldConfig['tab']); + + $formWidget->addFields(['viewBag[' . $fieldCode . ']' => $fieldConfig]); + } + } + + /** + * bindSyntaxFieldsWidget rebuilds the widget on any request carrying a page path. + * + * Nested widgets (repeater, mediafinder) fire their own AJAX handlers, which require the + * parent form to be re-bound to the controller on every request, not only on initial load. + */ + public function bindSyntaxFieldsWidget() + { + $path = trim((string) post('pagesSyntaxPath')); + if (strlen($path)) { + $tab = post('pagesSyntaxTab'); + $this->makeSyntaxFieldsWidget($path, is_string($tab) && strlen($tab) ? $tab : null); + } + } + + /** + * onLoadSyntaxFields renders one tab group of syntax fields for a page over AJAX. + */ + public function onLoadSyntaxFields() + { + $path = trim((string) post('path')); + $tab = post('tab'); + $tab = is_string($tab) && strlen($tab) ? $tab : null; + + $widget = $this->makeSyntaxFieldsWidget($path, $tab); + + $containerId = trim((string) post('containerId')) ?: 'pagesSyntaxFieldsForm'; + + if (!$widget) { + return ['#'.$containerId => '']; + } + + // Hidden fields let the per-request rebind (bindSyntaxFieldsWidget) rebuild the exact + // same widget so nested repeater/mediafinder AJAX handlers resolve. + $hidden = '' + .''; + + return [ + '#'.$containerId => $hidden.$widget->render(['useContainer' => false]) + ]; + } +} diff --git a/controllers/index/_concurrency_resolve_form.htm b/controllers/index/_concurrency_resolve_form.htm deleted file mode 100644 index 5e3d7be2..00000000 --- a/controllers/index/_concurrency_resolve_form.htm +++ /dev/null @@ -1,29 +0,0 @@ -'return false']) ?> - - - - \ No newline at end of file diff --git a/controllers/index/_content_toolbar.htm b/controllers/index/_content_toolbar.htm deleted file mode 100644 index 34f0268b..00000000 --- a/controllers/index/_content_toolbar.htm +++ /dev/null @@ -1,51 +0,0 @@ -
- - - - - - - - - - - - - - -
diff --git a/controllers/index/_form_page.htm b/controllers/index/_form_page.htm deleted file mode 100644 index d7d5873e..00000000 --- a/controllers/index/_form_page.htm +++ /dev/null @@ -1,25 +0,0 @@ - 'position-relative h-100', - 'data-change-monitor' => 'true', - 'data-window-close-confirm' => e(trans('backend::lang.form.confirm_tab_close')), - 'data-object-type' => e($objectType) -]) ?> - render() ?> - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/controllers/index/_menu_toolbar.htm b/controllers/index/_menu_toolbar.htm deleted file mode 100644 index 01207e7b..00000000 --- a/controllers/index/_menu_toolbar.htm +++ /dev/null @@ -1,51 +0,0 @@ -
- - - - - - - - - - - - - - -
diff --git a/controllers/index/_page_toolbar.htm b/controllers/index/_page_toolbar.htm deleted file mode 100644 index 6984ce22..00000000 --- a/controllers/index/_page_toolbar.htm +++ /dev/null @@ -1,61 +0,0 @@ -
- - - - - - - - - - - - - - - - - - - -
diff --git a/controllers/index/_sidepanel.htm b/controllers/index/_sidepanel.htm deleted file mode 100644 index 183a8393..00000000 --- a/controllers/index/_sidepanel.htm +++ /dev/null @@ -1,29 +0,0 @@ -
-
-
- -
- widget->pageList->render() ?> -
- - - -
- widget->menuList->render() ?> -
- - - -
- widget->contentList->render() ?> -
- - - -
- widget->snippetList->render() ?> -
- -
-
-
diff --git a/controllers/index/config_content_list.yaml b/controllers/index/config_content_list.yaml deleted file mode 100644 index 511203b4..00000000 --- a/controllers/index/config_content_list.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# =================================== -# Configures the layout list widget -# =================================== - -titleProperty: 'nice_title' -noRecordsMessage: 'cms::lang.content.no_list_records' -deleteConfirmation: 'cms::lang.content.delete_confirm_multiple' -itemType: content -controlClass: filelist-hero content -ignoreDirectories: - - static-pages* diff --git a/controllers/index/index.htm b/controllers/index/index.htm deleted file mode 100644 index de0f71bd..00000000 --- a/controllers/index/index.htm +++ /dev/null @@ -1,32 +0,0 @@ - - fatalError): ?> - makePartial('sidepanel') ?> - - - - - fatalError): ?> -
- -
-
- -
-
-
-
- -
- - -

fatalError)) ?>

- - diff --git a/controllers/index/index.php b/controllers/index/index.php new file mode 100644 index 00000000..d7ff4347 --- /dev/null +++ b/controllers/index/index.php @@ -0,0 +1,12 @@ + + fatalError): ?> +
+ + +
+ + + +

fatalError)) ?>

+ + diff --git a/docs/menu-item.png b/docs/menu-item.png deleted file mode 100644 index a2d8d17d963ad11d71fbec3d7eef02c87b8ee27a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41213 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%0DM;1)Oi@m^v*3KM#;WT4%+>RF(kSsr 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 z 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�&*ju;_n_Yr(`y6{>=A8 zBCTe=@&XHcS>mrSRk$(2Q}5UPv18*v;JAFm1Ji%G3PEJ?Z}|Lil;%P{ejm8%C8XZr zZ_Qr6)wv4-e-DXo^IvR=ui_?$R_Ay%`s~4B zG3<4c{pCJ*FV-*`W}30AV|MaxqSMr2cdFc|8F>2KYc6f_C@J(8HQ*<;rHeaR*0dSV zNphDNz1A=2M6Z}X36QpM7kbW-8=x?2<3Nv9Cg7PD3OP{la zb~Z@k$B^za;j--yG~RO*TKXUw|E1uUWeBLh7uE9m*|sgSt3mhh z-BlegL-8@hiw?7I+oy*cKskqVh;veyhybFFe8p`gvB)!a+K=G3Q<%{A0;gSV8B-#G&;{$9E_V4-UJfBuC~fg$~#u z#FCe*ZyAxQuY5Jm?9$uu*f~O>n}cTJWAyn9u^me7uKo?XoUjkBN>b9-b2=|tj)e^s zxH2jh4_*!qh$@pKIp$B)qMok8iG&7G+byjEr#URA%c--*{W2M&qw}lts+xBDe+q?5EK2An)d<+P!F$Uh+VK+RZMB+dr#2CPUY=`}Y#6y>jd0~AA zCzWCo&fcMD6L{zLd|gcJRQ*><_H>K&rf+m>*>zFq3pV@D7}a73q{(KRQ}c5yr=#19 zjuW*$LGOTkZFuB5k?3S699Ag+IV>h#&TAA;NNSi`qps_Ik-OVu*|RXhGYq+X1R(Hp z%(dq^WAo1(>O?Qa%~x2`!I`3};!6kwq|RBm8oC^uP}Xj@nG(Jb+AO+DkeTkpe2;h` z1i4)T9=cGuQm(4p9h=bL9+-i!) zNdQID%~>Hgh(T`f#cr$y;e9=Z+!K>MGJPeQB2mGQu|0WakVGFpU`w$ZBvsGl;`4QH z_qU(*gkM_F_4k0u z=|Td=Mm0CvQ(}!Aw?D!a?d`q;tUKVprB!okvT;H}L{IF^{OqgoM!%i~i5JJ-PD@Rxdu!vb8!Sh>ehJzF_cto$Ipr3VpBlyd&RDuLY*{eNXJs3Gdn1v>}#(ly;=Ow z>I1v8-|w?C^CoPfH958S&3(+Fe0FvNwKsU>?SHZuA>fVvnU}6*MOvI-rnwMIP#>Wo z>j4>Y`8D`P+r{hRp8Lm^#Q<*|2M#DH1dytKktq8=V4c4k!?yuL!2b8|4}LB-AQr18 z7+S(p#LwOMJ;c<>NsQh9MIg{{$EtD#D2rlXU;r&x#_#w(ht;9}#N$ech}#IY!}Zi0 zHJYrsOk1&I{`B?-tNLp3jV(X#L%5fwot?LS&E$jiwyz%G!_hx0N%js}OA38hPj^J) zDS%F#>wWhQhSwxBKFv)C?o$Z3+sN;9u+Wld zX9OnUwj-v^4s>q*c1`w{Td}9GHY-mmyZ7a$Iouy+@a?szm|5HplTGHp3&%j$X^O2? zv#kF#XD2(cGl^Vix`(`C zedDolK-|s_=?TDd7v=LeEwK4$wf#!NbIHc|c>WnC*Ah(*c6Lge&z7?9or|jUzj$zR z?Pq&SWv5AwK^tm=gM+DCml8`{!+JQ<>${sbeY9P*z#Ej&`orXn40JO=<>Y$Zg#~LX zH|G~E?>*1Q#~1i~c8CFbnyvLqvxlW1!;IZ&QgCF_%@ZJe{+c3YHlGi>^ILX?liLBb{S3ySsQDeJO9luvW8*sn=wYUHYdresTg`A}jgM0cav+K?eIbOr)Cnc$xKPWUTvB z;aJ#hzjH;icE`lSfJ6=t=l@O~`OhW?M4j{)@9{W(J|h6?mjG`4&KJv?1Fzie+4#ml zd<4cNp{ijKw5b;?E)V@9cd!SFUIBDDA3AFJVkbX``AU15JyB=rPy#M=M}h=?t1v%s zsOrsnTgRj_=)9ItR)9fsVL6`h4YLgX&VrIlG2BF<#j>a1*1qx&0Thx8${GJiaZad7 z!AX`SZ^!Bjatj4j0eK_-fAU6aMry1P@vgY$m7=OsLv5A3>Sncg1SMQr@QPF!lZiD9 zfE#>!XRQq|2xVXAPUa!o%75gK*s_a%WTXYBmic`t_XN;9M!Zu*`Y@%~;H2bp)qAyN zrAf+{0Q~ayvwEg3U%>FA)g6di{|Fp}3(~|Fmp0mw(ve&G%Y?Q+F-OCnnFEE>99q^Z zR2Wj!O}RfPRnxk~_rVsedRR8hgd67?bn3mx1%yZYawyZDLlQna|6(tEUpd^d^CXWJ z8<-o&H<**NSe+#pHd${jcRl|qaN_$}5*e!-A4&Y8(;w0nOKShLV5aIe(6K7Xmkx~| zvV7@sTJsCJF7Oh+JzM*4of50_w{#sT1lgT9%-~d#vkwpt9}9)mo(M&kqyLYHB=28x(wCUwLazK@}wWkS(1eNUkAO3;w+oUsK~`CDZ!Nw z5ZRoHd!Jt>2ea=`;uh7=9xL6hu}3Z(r9t;5RGES_I*TtdGEQt^`!iygQit-E%Q>b@ z81AF74xy%T)|h;UF{2yZPH1~acSXEmHgxd>uoW;+TQTV0V=fvZwIR$Af;Nm}BDQics=xAP`$2D(a~Z4Z)#qy%$U)wT@BLLcg{m5ZAYs6iBwzI$x0LwUWG zPE+$Z#Kd?g7GC$hzIc^00SU`m27JPRe_Hsp=8=~|^VnqLZz~lg9NW2hSCxc#CYwLmGH1+0c%#2Ib-6CR|Sl~XD5!B!-bhg zD;z9@<7o-NsH)Rk=qsMgHDERe}JxK_UVaAOexz|Kw&U4cYkUDj;PBtRWf*a3;S7nCx0@ zKmjm_>#)C_2}mFsO|O#)t{aEy&1&kuc)bNK$O;;H+enyvyw~q�DmH_HB}Mx89T> z)$d89#ko&Te5|NZys!99vhNkS)gG;xVpM5A_O3&9Py;!)x{(xSh?)-KOxQ_pkgmp= zTDT|1S{`=UnXk`30;;!`iEP4E)ZdegG{!CxrDbxd7US46>hY!cyw;E3jTgm~bxK33 zn)IFN(Wk-t9u(2I?xRwmQq&X8xu98IE<{NX>bQR*#n5xTq&%)sgSb;neLs%#Zl+#u zD_Xdf?o%juMR+#t);@)|vtm<*3{6ciDRt-KiejljZRMd&V|GfWCPTz<@EA-ZooHNe zu*&e&$9GmNe$8hxD;s-n)_hg7S3=ysJ`xz!hX%~O0`<0BH_Cejp{s~glO7p-Q@f`L z<2jsq>JAjYwezN;Hr*9|rDBPLQ5`tgAav(~`x<%1L&@hMx^6lifj~GGBOFt1LF|Wm zCx%E9?xB!my=xsQ$xD>4v$gdnOCFup*wMQdrrH__X%+9_$-5oz8NV_( z;lxuaXV@5xxoqQiO)#$N_-&uq(e%~TzOBXg*pYcDsVYI^0u=oU=1bz^8%PJWCW{Y) z6EjbIsCtl|J;#92YGQe%;g{W+2SVJoH@*r^Ryz|K+Ut%1kxxwMn@DL_xgtM*>a&@} z#P>flN+0PYKaaIR^sUz?JQLe@w;!$x2(GM=@f$j;)@?UZNf4IZ%+*}~yuVoxr&C#@aqnI2qp9EE>pDulJv-gG$x>)$@XS4h=Q7%>Oslu1_l!b`nZM~ zVJj<2Gs2c---lG2z&Hj?Gm0-r^u7rc-ZvV`O5S!35hGIDpAo!KeH!@OILFDfJFdHT zl_n<*ZyVLD52=Zzaf1`ma`WX?j;Z>W?oFyV>vbVV6!P+lM)^*62R5%Hhi=fxM75`O zu)X|sVJ`!NkV#h;&Z3~al$wwlckWYM`lQ|L}^nD8vG6b*l7SmWru@m|9*QU(Tt)i_((%&VB@Vp3DQJ13gM3YU7;JxtLy33JwI z4KICnJp`0H(Id?#JKoR%54$m5&*fx=gYAdAe&R(T^L**4k1?QOh!|7^-qO)5B%my) z>lnJJOs^g$C%mw9qXUa%C`YIFvPIi)CO{kNl0U{>liPfP%?&mj#w4tb+<{Ic>H7@_ zi3h7x+*)q#?+~tBBbRiHge_EG??085A=1-TryxKh68{)IR$-inH7YG9K%y>JwXLNs*F9ak5 z`cZat)DlRs0o)%f0uVz}2jDPyNR2S?s?+AC5PzRAz;23%1M3Vhl^0uqGuxbCgU4Aw zy@2T{|NM_L4_;KO$}rt8K^Oyz6iBjpzjBePz`WR85&MP6RV6 y;3@5xX|@CZ3h;w0G(6`3?fhH(V%R@CM#JY5N)BAlpa52xMiqrdmfU^t?7sj;#e#hR diff --git a/docs/snippets-backend.png b/docs/snippets-backend.png deleted file mode 100644 index 83df6ecef7f487c6ff628ec4cd2d374ccf284aad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32717 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�hL$=FJU#j0v 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 DPUDZN diff --git a/docs/static-page.png b/docs/static-page.png deleted file mode 100644 index 428330583384c7d518b0821751f7bc69acb3ad25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104609 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 - - return $this->makePartial('formcomponents', ['components' => $components]); - } - - /** - * listComponents - */ - protected function listComponents() - { - $result = []; - - if (!isset($this->model->settings['components'])) { - return $result; - } - - $manager = ComponentManager::instance(); - $manager->listComponents(); - - foreach ($this->model->settings['components'] as $name => $properties) { - list($name, $alias) = strpos($name, ' ') ? explode(' ', $name) : [$name, $name]; - - try { - $componentObj = $manager->makeComponent($name, null, $properties); - $componentObj->alias = $alias; - $componentObj->pluginIcon = $manager->findComponentOwnerDetails($componentObj)['icon'] ?? 'icon-puzzle-piece'; - } - catch (Exception $ex) { - $componentObj = new UnknownComponent(null, $properties, $ex->getMessage()); - $componentObj->alias = $alias; - $componentObj->pluginIcon = 'icon-bug'; - } - - $result[] = $componentObj; - } - - return $result; - } - - /** - * getComponentName - */ - protected function getComponentName($component) - { - return ComponentHelpers::getComponentName($component); - } - - /** - * getComponentDescription - */ - protected function getComponentDescription($component) - { - return ComponentHelpers::getComponentDescription($component); - } - - /** - * getComponentsPropertyConfig - */ - protected function getComponentsPropertyConfig($component) - { - return ComponentHelpers::getComponentsPropertyConfig($component); - } - - /** - * getComponentPropertyValues - */ - protected function getComponentPropertyValues($component) - { - return ComponentHelpers::getComponentPropertyValues($component); - } -} diff --git a/formwidgets/MenuItemSearch.php b/formwidgets/MenuItemSearch.php index d1bf49da..e8ec22b6 100644 --- a/formwidgets/MenuItemSearch.php +++ b/formwidgets/MenuItemSearch.php @@ -1,25 +1,18 @@ prepareVars(); - - return $this->makePartial('menuitems'); - } - - /** - * Prepares the list data - */ - public function prepareVars() - { - $menuItem = new MenuItem; - - $this->vars['itemProperties'] = json_encode($menuItem->fillable); - $this->vars['items'] = $this->model->items; - - $emptyItem = new MenuItem; - $emptyItem->title = trans($this->newItemTitle); - $emptyItem->type = 'url'; - $emptyItem->url = '/'; - - $this->vars['emptyItem'] = $emptyItem; - - $widgetConfig = $this->makeConfig('~/plugins/rainlab/pages/classes/menuitem/fields.yaml'); - $widgetConfig->model = $menuItem; - $widgetConfig->alias = $this->alias.'MenuItem'; - - $this->vars['itemFormWidget'] = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); - } - - /** - * {@inheritDoc} - */ - protected function loadAssets() - { - $this->addJs('js/menu-items-editor.js', 'RainLab.Pages'); - } - - /** - * {@inheritDoc} - */ - public function getSaveValue($value) - { - return strlen($value) ? $value : null; - } - - // - // Methods for the internal use - // - - /** - * Returns the item reference description. - * @param \RainLab\Pages\Classes\MenuItem $item Specifies the menu item - * @return string - */ - protected function getReferenceDescription($item) - { - if ($this->typeListCache === false) { - $this->typeListCache = $item->getTypeOptions(); - } - - if (!isset($this->typeInfoCache[$item->type])) { - $this->typeInfoCache[$item->type] = MenuItem::getTypeInfo($item->type); - } - - if (isset($this->typeInfoCache[$item->type])) { - $result = trans( - is_array($this->typeListCache[$item->type]) - ? $this->typeListCache[$item->type][0] - : $this->typeListCache[$item->type] - ); - - if ($item->type !== 'url') { - if (isset($this->typeInfoCache[$item->type]['references'])) { - $result .= ': '.$this->findReferenceName($item->reference, $this->typeInfoCache[$item->type]['references']); - } - } - else { - $result .= ': '.$item->url; - } - - } - else { - $result = trans('rainlab.pages::lang.menuitem.unknown_type'); - } - - return $result; - } - - protected function findReferenceName($search, $typeOptionList) - { - $iterator = function($optionList, $path) use ($search, &$iterator) { - foreach ($optionList as $reference => $info) { - if ($reference == $search) { - $result = $this->getMenuItemTitle($info); - - return strlen($path) ? $path.' / ' .$result : $result; - } - - if (is_array($info) && isset($info['items'])) { - $result = $iterator($info['items'], $path.' / '.$this->getMenuItemTitle($info)); - - if (strlen($result)) { - return strlen($path) ? $path.' / '.$result : $result; - } - } - } - }; - - $result = $iterator($typeOptionList, null); - if (!strlen($result)) { - $result = trans('rainlab.pages::lang.menuitem.unnamed'); - } - - $result = preg_replace('|^\s+\/|', '', $result); - - return $result; - } - - protected function getMenuItemTitle($itemInfo) - { - if (is_array($itemInfo)) { - if (!array_key_exists('title', $itemInfo) || !strlen($itemInfo['title'])) { - return trans('rainlab.pages::lang.menuitem.unnamed'); - } - - return $itemInfo['title']; - } - - return strlen($itemInfo) ? $itemInfo : trans('rainlab.pages::lang.menuitem.unnamed'); - } - - public function makeEditorTemplate() - { - $regEx = '##is'; - $partial = $this->makePartial('editorTemplate'); - - preg_match_all($regEx, $partial, $matches); - - $scripts = implode('', $matches[0]); - $template = preg_replace($regEx, '', $partial); - - return [$template, $scripts]; - } -} diff --git a/formwidgets/MenuPicker.php b/formwidgets/MenuPicker.php deleted file mode 100644 index 5ba38f4e..00000000 --- a/formwidgets/MenuPicker.php +++ /dev/null @@ -1,49 +0,0 @@ -prepareVars(); - - return $this->makePartial('~/modules/backend/widgets/form/partials/_field_dropdown.htm'); - } - - /** - * Prepares the view data - */ - public function prepareVars() - { - $this->vars['field'] = $this->makeFormField(); - } - - protected function makeFormField(): FormField - { - $field = clone $this->formField; - $field->type = 'dropdown'; - $field->options = $this->getOptions(); - - return $field; - } - - protected function getOptions(): array - { - return Menu::listInTheme(Theme::getEditTheme(), true) - ->mapWithKeys(function ($menu) { - return [ - $menu->code => $menu->name, - ]; - })->toArray(); - } -} diff --git a/formwidgets/PagePicker.php b/formwidgets/PagePicker.php deleted file mode 100644 index 5c6e0115..00000000 --- a/formwidgets/PagePicker.php +++ /dev/null @@ -1,64 +0,0 @@ -prepareVars(); - return $this->makePartial('~/modules/backend/widgets/form/partials/_field_dropdown.htm'); - } - - /** - * Prepares the view data - */ - public function prepareVars() - { - $this->vars['field'] = $this->makeFormField(); - } - - /** - * @return \Backend\Classes\FormField - */ - protected function makeFormField() - { - $field = clone $this->formField; - $field->type = 'dropdown'; - - $tree = Page::buildMenuTree(Theme::getEditTheme()); - $indent = $field->getConfig('indent', $this->indent); - - // Flatten page tree for dropdown options - $options = []; - $iterator = function($items, $depth = 0) use (&$iterator, &$tree, &$options, $indent) { - - foreach ($items as $code) { - $itemData = $tree[$code]; - $options[$code] = str_repeat($indent, $depth) . $itemData['title']; - if (!empty($itemData['items'])) { - $iterator($itemData['items'], $depth+1); - } - } - - return $options; - }; - - $field->options = $iterator($tree['--root-pages--']); - - return $field; - } -} diff --git a/formwidgets/components/partials/_component.htm b/formwidgets/components/partials/_component.htm deleted file mode 100644 index 0707d8ba..00000000 --- a/formwidgets/components/partials/_component.htm +++ /dev/null @@ -1,17 +0,0 @@ -
-
inspectorEnabled): ?>data-inspectable - data-inspector-title="getComponentName($component)) ?>" - data-inspector-description="getComponentDescription($component)) ?>" - data-inspector-config="getComponentsPropertyConfig($component)) ?>" - data-inspector-class=""> - - - alias) ?> - - - - × -
-
\ No newline at end of file diff --git a/formwidgets/components/partials/_formcomponents.htm b/formwidgets/components/partials/_formcomponents.htm deleted file mode 100644 index 0f22cbb2..00000000 --- a/formwidgets/components/partials/_formcomponents.htm +++ /dev/null @@ -1,17 +0,0 @@ -
- - isHidden): ?> - makePartial('component', ['component' => $component]) ?> - - - -
-
- - isHidden): ?> - makePartial('component', ['component' => $component]) ?> - - -
-
-
diff --git a/formwidgets/menuitems/assets/js/menu-items-editor.js b/formwidgets/menuitems/assets/js/menu-items-editor.js deleted file mode 100644 index 7e94ef26..00000000 --- a/formwidgets/menuitems/assets/js/menu-items-editor.js +++ /dev/null @@ -1,651 +0,0 @@ -/* - * The menu item editor. Provides tools for managing the - * menu items. - */ -+function ($) { "use strict"; - var MenuItemsEditor = function (el, options) { - this.$el = $(el) - this.options = options - - this.init() - } - - MenuItemsEditor.prototype.init = function() { - var self = this - - this.alias = this.$el.data('alias') - this.$treeView = this.$el.find('div[data-control="treeview"]') - - this.typeInfo = {} - - // Menu items is clicked - this.$el.on('open.oc.treeview', function(e) { - return self.onItemClick(e.relatedTarget) - }) - - // Submenu item is clicked in the master tabs - this.$el.on('submenu.oc.treeview', $.proxy(this.onSubmenuItemClick, this)) - - this.$el.on('click', 'a[data-control~="add-item"]', function(e) { - self.onCreateItem(e.target) - return false - }) - } - - /* - * Triggered when a submenu item is clicked in the menu editor. - */ - MenuItemsEditor.prototype.onSubmenuItemClick = function(e) { - if ($(e.relatedTarget).data('control') == 'delete-menu-item') - this.onDeleteMenuItem(e.relatedTarget) - - if ($(e.relatedTarget).data('control') == 'create-item') - this.onCreateItem(e.relatedTarget) - - return false - } - - /* - * Removes a menu item - */ - MenuItemsEditor.prototype.onDeleteMenuItem = function(link) { - if (!confirm('Do you really want to delete the menu item? This will also delete the subitems, if any.')) - return - - $(link).trigger('change') - $(link).closest('li[data-menu-item]').remove() - - $(window).trigger('oc.updateUi') - - this.$treeView.treeView('update') - this.$treeView.treeView('fixSubItems') - } - - /* - * Opens the menu item editor - */ - MenuItemsEditor.prototype.onItemClick = function(item, newItemMode) { - var $item = $(item), - $container = $('> div', $item), - self = this - - $container.one('show.oc.popup', function(e) { - self.triggerRenderEvent(); - - self.$popupContainer = $(e.relatedTarget); - self.$itemDataContainer = $container.closest('li') - - $('input[type=checkbox]', self.$popupContainer).removeAttr('checked') - - self.loadProperties(self.$popupContainer, self.$itemDataContainer.data('menu-item')) - self.$popupForm = self.$popupContainer.find('form') - self.itemSaved = false - - var $titleField = $('input[name=title]', self.$popupContainer).focus().select() - var $typeField = $('select[name=type]', self.$popupContainer).change(function(){ - self.loadTypeInfo(false, true) - }) - - $('select[name=reference]', self.$popupContainer).change(function() { - var selectedTitle = $(this).find('option:selected').text(); - // If the saved title is the default new item title, use reference title, - // removing CMS page [base file name] suffix - if (selectedTitle && self.properties.title === self.$popupForm.attr('data-new-item-title')) { - var title = $.trim(selectedTitle.replace(/\s*\[.*\]$/, '')) - $titleField.val(title) - - // Support for RainLab.Translate - var defaultLocale = $('[data-control="multilingual"]').data('default-locale') - if (defaultLocale) { - $('[name="RLTranslate['+defaultLocale+'][title]"]', self.$popupContainer).val(title) - } - } - }) - - self.$popupContainer.on('keydown', function(e) { - if (e.which == 13) - self.applyMenuItem() - }) - - $('button[data-control="apply-btn"]', self.$popupContainer).click($.proxy(self.applyMenuItem, self)) - - var $updateTypeOptionsBtn = $('') - $('div[data-field-name=reference]').addClass('input-sidebar-control').append($updateTypeOptionsBtn) - - $updateTypeOptionsBtn.click(function(){ - self.loadTypeInfo(true) - - return false - }) - - $updateTypeOptionsBtn.keydown(function(ev){ - if (ev.which == 13 || ev.which == 32) { - self.loadTypeInfo(true) - return false - } - }) - - self.$popupContainer.on('change', 'select[name="referenceSearch"]', function() { - var $select = $(this), - val = $select.val(), - parts - - if (!val) return - - // type::reference ID - parts = val.split('::', 2) - - self.referenceSearchOverride = parts[1]; - - $select.empty().trigger('change.select2'); - - $typeField - .val(parts[0]) - .triggerHandler('change') - }) - - var $updateCmsPagesBtn = $updateTypeOptionsBtn.clone(true) - $('div[data-field-name=cmsPage]').addClass('input-sidebar-control').append($updateCmsPagesBtn) - - self.loadTypeInfo() - }) - - $container.one('hide.oc.popup', function(e) { - if (!self.itemSaved && newItemMode) - $item.remove() - - self.$treeView.treeView('update') - self.$treeView.treeView('fixSubItems') - - $container.removeClass('popover-highlight') - }) - - $container.popup({ - content: $('script[data-editor-template]', this.$el).html() - }) - - /* - * Highlight modal target - */ - $container.addClass('popover-highlight') - $container.blur() - - return false - } - - MenuItemsEditor.prototype.loadProperties = function($popupContainer, properties) { - this.properties = properties - - var setPropertyOnElement = function($input, val) { - if ($input.prop('type') == 'checkbox') { - var checked = !(val == '0' || val == 'false' || val == 0 || val == undefined || val == null) - checked ? $input.prop('checked', 'checked') : $input.removeAttr('checked') - } - else if ($input.prop('type') == 'radio') { - $input.filter('[value="'+val+'"]').prop('checked', true) - } - else { - $input.val(val) - $input.change() - } - } - - var defaultLocale = $('[data-control="multilingual"]', $popupContainer).data('default-locale') - $.each(properties, function(property, val) { - if (property == 'viewBag') { - $.each(val, function(vbProperty, vbVal) { - var $input = $('[name="viewBag['+vbProperty+']"]', $popupContainer).not('[type=hidden]') - setPropertyOnElement($input, vbVal) - // Ensure that locale specific data is made available in the RainLab.Translate data holders - if (vbProperty === 'locale') { - $.each(vbVal, function(locale, fields) { - $.each(fields, function(fieldName, fieldValue) { - var $locker = $('[name="RLTranslate['+locale+']['+fieldName+']"]', $popupContainer) - if ($locker) { - $locker.val(fieldValue) - } - }) - }) - } - }) - - /** - * Mediafinder support - */ - var mediafinderElements = $('[data-control="mediafinder"]', $popupContainer); - var storageMediaPath = $('[data-storage-media-path]').data('storage-media-path'); - - $.each(mediafinderElements, function() { - var input = $(this).find('input'), - propertyName = input.attr('name'), - propertyNameSimple; - - if (propertyName && propertyName.length) { - propertyNameSimple = propertyName.substr(8).slice(0, -1); - } - - var propertyValue = ''; - - $.each(val, function(vbProperty, vbVal) { - if (vbProperty == propertyNameSimple) { - propertyValue = vbVal; - } - }); - - if (propertyValue != '') { - // v2 media finder - var dataLocker = $('[data-data-locker]', this); - if (dataLocker.length) { - var items = [{ - path: propertyValue, - publicUrl: storageMediaPath + propertyValue, - thumbUrl: storageMediaPath + propertyValue, - title: propertyValue.substring(1) - }]; - - var mediafinder = $(this).data('oc.mediaFinder') || oc.observeControl(this, 'mediafinder'); - mediafinder.addItems(items); - mediafinder.setValue(); - mediafinder.evalIsPopulated(); - } - // v1 media finder - else { - $(this).toggleClass('is-populated'); - input.attr('value', propertyValue); - - var image = $('[data-find-image]', this); - if (image.length) { - image.attr('src', storageMediaPath + propertyValue); - } - - var file = $('[data-find-file-name]', this); - if (file.length) { - file.text(propertyValue.substring(1)); - } - } - } - }); - - } - else { - var $input = $('[name="'+property+'"]', $popupContainer).not('[type=hidden]') - setPropertyOnElement($input, val) - // If the RainLab.Translate default locale data locker fields are available make sure that they are properly populated - var $defaultLocaleField = $('[name="RLTranslate['+defaultLocale+']['+property+']"]', self.$popupContainer) - if ($defaultLocaleField) { - $defaultLocaleField.val($input.val()); - } - } - }) - } - - MenuItemsEditor.prototype.loadTypeInfo = function(force, focusList) { - var type = $('select[name=type]', this.$popupContainer).val() - - var self = this - - if (!force && this.typeInfo[type] !== undefined) { - self.applyTypeInfo(this.typeInfo[type], type, focusList) - return - } - - $.oc.stripeLoadIndicator.show() - this.$popupForm.request('onGetMenuItemTypeInfo') - .always(function(){ - $.oc.stripeLoadIndicator.hide() - }) - .done(function(data){ - self.typeInfo[type] = data.menuItemTypeInfo - self.applyTypeInfo(data.menuItemTypeInfo, type, focusList) - }) - } - - MenuItemsEditor.prototype.applyTypeInfo = function(typeInfo, type, focusList) { - var $referenceFormGroup = $('div[data-field-name="reference"]', this.$popupContainer), - $optionSelector = $('select', $referenceFormGroup), - $nestingFormGroup = $('div[data-field-name="nesting"]', this.$popupContainer), - $urlFormGroup = $('div[data-field-name="url"]', this.$popupContainer), - $replaceFormGroup = $('div[data-field-name="replace"]', this.$popupContainer), - $cmsPageFormGroup = $('div[data-field-name="cmsPage"]', this.$popupContainer), - $cmsPageSelector = $('select', $cmsPageFormGroup), - prevSelectedReference = $optionSelector.val(), - prevSelectedPage = $cmsPageSelector.val() - - // Search selection - if (this.referenceSearchOverride) { - prevSelectedReference = this.referenceSearchOverride; - this.referenceSearchOverride = null; - } - - if (typeInfo.references) { - $optionSelector.find('option').remove() - $referenceFormGroup.show() - - var iterator = function(options, level, path) { - $.each(options, function(code) { - var $option = $('').attr('value', code), - offset = Array(level*4).join(' '), - isObject = $.type(this) == 'object' - - $option.text(isObject ? this.title : this) - - var optionPath = path.length > 0 - ? (path + ' / ' + $option.text()) - : $option.text() - - $option.data('path', optionPath) - - $option.html(offset + $option.html()) - - $optionSelector.append($option) - - if (isObject) - iterator(this.items, level+1, optionPath) - }) - } - - iterator(typeInfo.references, 0, '') - - $optionSelector - .val(prevSelectedReference ? prevSelectedReference : this.properties.reference) - .triggerHandler('change') - } - else { - $referenceFormGroup.hide() - } - - if (typeInfo.cmsPages) { - $cmsPageSelector.find('option').remove() - $cmsPageFormGroup.show() - - $.each(typeInfo.cmsPages, function(code) { - var $option = $('').attr('value', code) - - $option.text(this).val(code) - $cmsPageSelector.append($option) - }) - - $cmsPageSelector - .val(prevSelectedPage ? prevSelectedPage : this.properties.cmsPage) - .triggerHandler('change') - } - else { - $cmsPageFormGroup.hide() - } - - $nestingFormGroup.toggle(typeInfo.nesting !== undefined && typeInfo.nesting) - $urlFormGroup.toggle(type == 'url') - $replaceFormGroup.toggle(typeInfo.dynamicItems !== undefined && typeInfo.dynamicItems) - - this.triggerRenderEvent(); - - if (focusList) { - var focusElements = [ - $referenceFormGroup, - $cmsPageFormGroup, - $('div.custom-checkbox', $nestingFormGroup), - $('div.custom-checkbox', $replaceFormGroup), - $('input', $urlFormGroup) - ] - - $.each(focusElements, function(){ - if (this.is(':visible')) { - var $self = this - - window.setTimeout(function() { - if ($self.hasClass('dropdown-field')) - $('select', $self).select2('focus', 100) - else $self.focus() - }) - - return false; - } - }) - } - } - - MenuItemsEditor.prototype.applyMenuItem = function() { - var self = this, - data = {}, - propertyNames = this.$el.data('item-properties'), - basicProperties = { - 'title': 1, - 'type': 1, - 'code': 1 - }, - typeInfoPropertyMap = { - reference: 'references', - replace: 'dynamicItems', - cmsPage: 'cmsPages' - }, - typeInfo = {}, - validationErrorFound = false - - // Ensure that locale specific data is made available in the RainLab.Translate data holders - $('[name^="viewBag[locale]"]', self.$popupContainer).each(function() { - var locale = $(this).data('locale') - var fieldName = $(this).data('field-name') - var $localeField = $('[name="RLTranslate['+locale+']['+fieldName+']"]', self.$popupContainer) - $(this).val($localeField.val()) - }); - - var defaultLocale = $('[data-control="multilingual"]').data('default-locale') - - $.each(propertyNames, function() { - var propertyName = this, - $input = $('[name="'+propertyName+'"]', self.$popupContainer).not('[type=hidden]') - - // If the RainLab.Translate default locale data locker fields are available make sure the regular inputs are properly populated - if (defaultLocale) { - var $defaultLocaleField = $('[name="RLTranslate['+defaultLocale+']['+propertyName+']"]', self.$popupContainer) - if ($defaultLocaleField && $defaultLocaleField.val()) { - $input.val($defaultLocaleField.val()) - } - } - - if ($input.prop('type') !== 'checkbox') { - data[propertyName] = $.trim($input.val()) - - if (propertyName == 'type') - typeInfo = self.typeInfo[data.type] - - if (data[propertyName].length == 0) { - var typeInfoProperty = typeInfoPropertyMap[propertyName] !== undefined ? typeInfoPropertyMap[propertyName] : propertyName - - if (typeInfo[typeInfoProperty] !== undefined) { - - $.oc.flashMsg({ - class: 'error', - text: self.$popupForm.attr('data-message-'+propertyName+'-required') - }) - - if ($input.prop("tagName") == 'SELECT') - $input.select2('focus') - else - $input.focus() - - validationErrorFound = true - - return false - } - } - } - else { - data[propertyName] = $input.prop('checked') ? 1 : 0 - } - }) - - if (validationErrorFound) - return - - if (data.type !== 'url') { - delete data['url'] - - $.each(data, function(property) { - if (property == 'type') - return - - var typeInfoProperty = typeInfoPropertyMap[property] !== undefined ? typeInfoPropertyMap[property] : property - if ((typeInfo[typeInfoProperty] === undefined || typeInfo[typeInfoProperty] === false) - && basicProperties[property] === undefined) - delete data[property] - }) - } - else { - $.each(propertyNames, function(){ - if (this != 'url' && basicProperties[this] === undefined) - delete data[this] - }) - } - - if ($.trim(data.title).length == 0) { - $.oc.flashMsg({ - class: 'error', - text: self.$popupForm.data('messageTitleRequired') - }) - - $('[name=title]', self.$popupContainer).focus() - - return - } - - if (data.type == 'url' && $.trim(data.url).length == 0) { - $.oc.flashMsg({ - class: 'error', - text: self.$popupForm.data('messageUrlRequired') - }) - - $('[name=url]', self.$popupContainer).focus() - - return - } - - $('> div span.title', self.$itemDataContainer).text(data.title) - - var referenceDescription = $.trim($('select[name=type] option:selected', self.$popupContainer).text()) - - if (data.type == 'url') { - referenceDescription += ': ' + $('input[name=url]', self.$popupContainer).val() - } - else if (typeInfo.references) { - referenceDescription += ': ' + $.trim($('select[name=reference] option:selected', self.$popupContainer).data('path')) - } - - $('> div span.comment', self.$itemDataContainer).text(referenceDescription) - - this.attachViewBagData(data) - - this.$itemDataContainer.data('menu-item', data) - this.itemSaved = true - this.$popupContainer.trigger('close.oc.popup') - this.$el.trigger('change') - } - - MenuItemsEditor.prototype.attachViewBagData = function(data) { - var fields = this.$popupForm.serializeArray(), - fieldName, - fieldValue - - $.each(fields, function(index, field) { - fieldName = field.name - fieldValue = field.value - - if (fieldName.indexOf('viewBag[') != 0) { - return true // Continue - } - - /* - * Break field name in to elements - */ - var elements = [], - searchResult, - expression = /([^\]\[]+)/g - - while ((searchResult = expression.exec(fieldName))) { - elements.push(searchResult[0]) - } - - /* - * Attach elements to data with value - */ - var currentData = data, - elementsNum = elements.length, - lastIndex = elementsNum - 1, - currentProperty - - for (var i = 0; i < elementsNum; ++i) { - currentProperty = elements[i] - - if (i === lastIndex) { - currentData[currentProperty] = fieldValue - } - else if (currentData[currentProperty] === undefined) { - currentData[currentProperty] = {} - } - - currentData = currentData[currentProperty] - } - }) - } - - MenuItemsEditor.prototype.onCreateItem = function(target) { - var parentList = $(target).closest('li[data-menu-item]').find(' > ol'), - item = $($('script[data-item-template]', this.$el).html()) - - if (!parentList.length) - parentList = $(target).closest('div[data-control=treeview]').find(' > ol') - - parentList.append(item) - this.$treeView.treeView('update') - $(window).trigger('oc.updateUi') - - this.onItemClick(item, true) - } - - MenuItemsEditor.prototype.triggerRenderEvent = function() { - // Vanilla AJAX Framework (v3) - if (window.oc && oc.Events) { - oc.Events.dispatch('render'); - } - // Classic AJAX Framework (v1,2) - else { - $(document).trigger('render'); - } - } - - MenuItemsEditor.DEFAULTS = { - } - - // MENUITEMSEDITOR PLUGIN DEFINITION - // ============================ - - var old = $.fn.menuItemsEditor - - $.fn.menuItemsEditor = function (option) { - var args = Array.prototype.slice.call(arguments, 1) - return this.each(function () { - var $this = $(this) - var data = $this.data('oc.menuitemseditor') - var options = $.extend({}, MenuItemsEditor.DEFAULTS, $this.data(), typeof option == 'object' && option) - if (!data) $this.data('oc.menuitemseditor', (data = new MenuItemsEditor(this, options))) - else if (typeof option == 'string') data[option].apply(data, args) - }) - } - - $.fn.menuItemsEditor.Constructor = MenuItemsEditor - - // MENUITEMSEDITOR NO CONFLICT - // ================= - - $.fn.menuItemsEditor.noConflict = function () { - $.fn.menuItemsEditor = old - return this - } - - // MENUITEMSEDITOR DATA-API - // =============== - - $(document).on('render', function() { - $('[data-control="menu-item-editor"]').menuItemsEditor() - }); -}(window.jQuery); diff --git a/formwidgets/menuitems/partials/_editortemplate.htm b/formwidgets/menuitems/partials/_editortemplate.htm deleted file mode 100644 index fc49f10d..00000000 --- a/formwidgets/menuitems/partials/_editortemplate.htm +++ /dev/null @@ -1,29 +0,0 @@ - - - \ No newline at end of file diff --git a/formwidgets/menuitems/partials/_item.htm b/formwidgets/menuitems/partials/_item.htm deleted file mode 100644 index 2c320a85..00000000 --- a/formwidgets/menuitems/partials/_item.htm +++ /dev/null @@ -1,38 +0,0 @@ - -
  • - - -
      - items): ?> - makePartial('itemlist', ['items' => $subItems]) ?> - -
    -
  • \ No newline at end of file diff --git a/formwidgets/menuitems/partials/_itemlist.htm b/formwidgets/menuitems/partials/_itemlist.htm deleted file mode 100644 index 8a061b8e..00000000 --- a/formwidgets/menuitems/partials/_itemlist.htm +++ /dev/null @@ -1,5 +0,0 @@ - - - makePartial('item', ['item' => $item]) ?> - - \ No newline at end of file diff --git a/formwidgets/menuitems/partials/_items.htm b/formwidgets/menuitems/partials/_items.htm deleted file mode 100644 index 8f1d805b..00000000 --- a/formwidgets/menuitems/partials/_items.htm +++ /dev/null @@ -1,11 +0,0 @@ -
      - makePartial('itemlist', ['items' => $items]) ?> -
    - - - - \ No newline at end of file diff --git a/formwidgets/menuitems/partials/_menuitems.htm b/formwidgets/menuitems/partials/_menuitems.htm deleted file mode 100644 index e9b5c7bd..00000000 --- a/formwidgets/menuitems/partials/_menuitems.htm +++ /dev/null @@ -1,36 +0,0 @@ -makeEditorTemplate(); -?> -
    -
    -
    - makePartial('items', ['items' => $items]) ?> -
    -
    - - - - - - -
    diff --git a/lang/cs/lang.php b/lang/cs/lang.php deleted file mode 100644 index 7eca2457..00000000 --- a/lang/cs/lang.php +++ /dev/null @@ -1,105 +0,0 @@ - [ - 'name' => 'Stránky', - 'description' => 'Funkce pro správu stránek a menu.', - ], - 'page' => [ - 'menu_label' => 'Stránky', - 'template_title' => '%s Stránky', - 'delete_confirmation' => 'Opravdu chcete odstranit vybrané stránky? Budou odstraněny i případné podstránky.', - 'no_records' => 'Stránky nenalezeny', - 'delete_confirm_single' => 'Opravu chcete odstranit tuto stránku? Budou odstraněny i případné podstránky.', - 'new' => 'Nová stránka', - 'add_subpage' => 'Přidat podstránku', - 'invalid_url' => 'Neplatný formát URL. URL by mělo začínat lomítkem a může obsahovat čísla, písmena a znaky: _-/', - 'url_not_unique' => 'Toto URL je používáno jinou stránkou.', - 'layout' => 'Layouty', - 'layouts_not_found' => 'Layouts nenalezeny', - 'saved' => 'Stránka byla úspěšně uložena.', - 'tab' => 'Stránky', - 'manage_pages' => 'Spravovat stránky', - 'manage_menus' => 'Spravovat menu', - 'access_snippets' => 'Používat snippety', - 'manage_content' => 'Spravovat obsah', - ], - 'menu' => [ - 'menu_label' => 'Menu', - 'delete_confirmation' => 'Opravdu chcete odstranit vybraná menu?', - 'no_records' => 'Položky nenalezeny', - 'new' => 'Nové menu', - 'new_name' => 'Nové menu', - 'new_code' => 'nove-menu', - 'delete_confirm_single' => 'Opravdu chcete odstranit toto menu?', - 'saved' => 'Menu bylo úspěšně uloženo.', - 'name' => 'Název', - 'code' => 'Kód', - 'items' => 'Položky menu', - 'add_subitem' => 'Přidat položku', - 'code_required' => 'Pole kód je povinné.', - 'invalid_code' => 'Pole kód obsahuje neplatné znaky. Může obsahovat pouze číslice, písmena a znaky: _-', - ], - 'menuitem' => [ - 'title' => 'Titulek', - 'editor_title' => 'Upravit položku menu', - 'type' => 'Typ', - 'allow_nested_items' => 'Povolit vnořené položky', - 'allow_nested_items_comment' => 'Vnořené položky mohou být automaticky vygenerovány statickými stránkami nebo jinými typy stránek.', - 'url' => 'URL', - 'reference' => 'Odkaz', - 'search_placeholder' => 'Prohledat odkazy...', - 'title_required' => 'Titulek je povinný', - 'unknown_type' => 'Neznámý typ položky', - 'unnamed' => 'Nepojmenovaný typ položky', - 'add_item' => 'Přidat položku', - 'new_item' => 'Nová položka menu', - 'replace' => 'Nahradit tuto položku jejími vygenerovanými vnořenými položkami', - 'replace_comment' => 'Toto pole zaškrtněte, pokud si přejete vnořené položky posunout na stejnou úroveň jako má tato položka. Samotná položka zůstane skryta.', - 'cms_page' => 'CMS stránka', - 'cms_page_comment' => 'Vyberte stránku, která se otevře při kliknutí na tuto položku v menu.', - 'reference_required' => 'Odkaz je povinný.', - 'url_required' => 'URL je povinné', - 'cms_page_required' => 'Prosím vyberte CMS stránku', - 'display_tab' => 'Zobrazení', - 'hidden' => 'Skrytá', - 'hidden_comment' => 'Skrýt tuto položku menu pro celý front-end.', - 'attributes_tab' => 'Vlastnosti', - 'code' => 'Kód', - 'code_comment' => 'Zadejte kód položky menu pokud k ní chcete přistupovat přes API.', - 'css_class' => 'CSS třída', - 'css_class_comment' => 'Vložte název CSS třídy k zajištění specifického vzhledu této položky menu.', - 'external_link' => 'Externí odkaz', - 'external_link_comment' => 'Otevřít odkaz této položky v novém okně.', - 'static_page' => 'Statická stránka', - 'all_static_pages' => 'Všechny statické stránky', - ], - 'content' => [ - 'menu_label' => 'Obsah', - 'cant_save_to_dir' => 'Ukládat obsah do složky statických stránek není povoleno.', - ], - 'sidebar' => [ - 'add' => 'Přidat', - ], - 'object' => [ - 'invalid_type' => 'Neznámý typ objektu', - 'not_found' => 'Požadovaný objekt nebyl nalezen.', - ], - 'editor' => [ - 'title' => 'Titulek', - 'new_title' => 'Nový titulek stránky', - 'content' => 'Obsah', - 'url' => 'URL', - 'filename' => 'Název souboru', - 'layout' => 'Layout', - 'description' => 'Popis', - 'preview' => 'Náhled', - 'enter_fullscreen' => 'Vstoupit do režimu celé obrazovky', - 'exit_fullscreen' => 'Opustit režim celé obrazovky', - 'hidden' => 'Skrytá', - 'hidden_comment' => 'Skryté stránky jsou dostupné pouze přihlášeným administrátorům.', - 'navigation_hidden' => 'Skrýt v menu', - 'navigation_hidden_comment' => 'Zaškrtněte toto pole, pokud chcete stránku skrýt z automaticky vygenerovaných menu a drobečkové navigace.', - ], - 'snippet' => [ - 'menu_label' => 'Snippety', - ], -]; diff --git a/lang/de/lang.php b/lang/de/lang.php deleted file mode 100644 index 918ee487..00000000 --- a/lang/de/lang.php +++ /dev/null @@ -1,93 +0,0 @@ - [ - 'name' => 'Seiten', - 'description' => 'Pages & menus features.', - ], - 'page' => [ - 'menu_label' => 'Seiten', - 'template_title' => '%s Seiten', - 'delete_confirmation' => 'Möchten Sie die ausgewählten Seiten wirklich löschen? Dadurch werden auch mögliche Unterseiten gelöscht.', - 'no_records' => 'Keine Seiten gefunden', - 'delete_confirm_single' => 'Möchten Sie die ausgewählte Seite wirklich löschen? Dadurch werden auch mögliche Unterseiten gelöscht.', - 'new' => 'Neue Seite', - 'add_subpage' => 'Neue Unterseite', - 'invalid_url' => 'Ungültiges URL Format. Die URL sollte mit einem Schrägstrich (Slash) starten und darf Ziffern, Buchstaben und folgenden Symbole enthalten: _-/.', - 'url_not_unique' => 'Die URL wird schon von einer anderen Seite benutzt.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Keine Layouts gefunden', - 'saved' => 'Die Seite wurde erfolgreich gespeichert.', - 'manage_pages' => 'Verwalte statische Seiten', - 'manage_menus' => 'Verwalte statische Menüs', - 'access_snippets' => 'Verwalte Snippets', - 'manage_content' => 'Verwalte den Inhalt', - ], - 'menu' => [ - 'menu_label' => 'Menüs', - 'delete_confirmation' => 'Möchten Sie die ausgewählten Menüs wirklich löschen?', - 'no_records' => 'Keine Menüpunkte gefunden', - 'new' => 'Neues Menü', - 'new_name' => 'Menüname', - 'new_code' => 'menuename', - 'delete_confirm_single' => 'Möchten Sie das ausgewählte Menü wirklich löschen?', - 'saved' => 'Das Menü wurde erfolgreich gespeichert.', - 'name' => 'Name', - 'code' => 'Code', - 'items' => 'Menüpunkte', - 'add_subitem' => 'Neuer Menüpunkt', - 'code_required' => 'Ein Code ist erforderlich', - 'invalid_code' => 'Ungültiges Code Format. Der Code darf Ziffern, Buchstaben und folgenden Symbole enthalten: _-/', - ], - 'menuitem' => [ - 'title' => 'Titel', - 'editor_title' => 'Menüpunkt bearbeiten', - 'type' => 'Typ', - 'allow_nested_items' => 'Erlaube verschachtelte Menüpunkte', - 'allow_nested_items_comment' => 'Verschachtelte Menüpunkte können dynamisch durch statische Seiten und einigen anderen Menüpunkt-Typen erzeugt werden.', - 'url' => 'URL', - 'reference' => 'Referenz', - 'title_required' => 'Ein Titel ist erforderlich', - 'unknown_type' => 'Unbekannter Menüpunkt-Typ', - 'unnamed' => 'Unbekannter Menüpunkt', - 'add_item' => 'Neuer Menüpunkt', - 'new_item' => 'Neuer Menüpunkt', - 'replace' => 'Ersetze diesen Menüpunkt mit seinen Unterpunkten', - 'replace_comment' => 'Verwenden Sie diese Option, um erzeugte Menüpunkte auf die gleiche Ebene von diesem zu bringen. Dieser Menüpunkt selbst wird ausgeblendet.', - 'cms_page' => 'CMS Seite', - 'cms_page_comment' => 'Wählen Sie eine Seite die geöffnet werden soll, wenn dieser Menüpunkt angeklickt wird.', - 'reference_required' => 'Eine Menüpunkt-Referenz ist erforderlich', - 'url_required' => 'Eine URL ist erforderlich', - 'cms_page_required' => 'Bitten wählen Sie eine CMS Seite', - 'code' => 'Code', - 'code_comment' => 'Geben Sie einen Menüpunkt-Code ein, wenn Sie diesen mit der API ansprechen möchten.', - ], - 'content' => [ - 'menu_label' => 'Inhalte', - 'cant_save_to_dir' => 'Das Speichern von Inhaltsdateien in den Statische-Seiten Ordner ist nicht erlaubt.', - ], - 'sidebar' => [ - 'add' => 'Neu', - ], - 'object' => [ - 'invalid_type' => 'Unbekannter Objekttyp', - 'not_found' => 'Das angeforderte Objekt wurde nicht gefunden.', - ], - 'editor' => [ - 'title' => 'Titel', - 'new_title' => 'Titel für die neue Seite', - 'content' => 'Inhalt', - 'url' => 'URL', - 'filename' => 'Dateiname', - 'layout' => 'Layout', - 'description' => 'Beschreibung', - 'preview' => 'Vorschau', - 'enter_fullscreen' => 'Vollbildmodus einschalten', - 'exit_fullscreen' => 'Vollbildmodus verlassen', - 'hidden' => 'Verstecken', - 'hidden_comment' => 'Versteckte Seiten sind nur für eingeloggte administrations Benutzer zugänglich.', - 'navigation_hidden' => 'In der Navigation verstecken', - 'navigation_hidden_comment' => 'Setzen Sie diese Option, um diese Seite von automatisch generierten Menüs und Breadcrumbs zu verstecken.', - ], - 'snippet' => [ - 'menu_label' => 'Snippets', - ], -]; diff --git a/lang/el/lang.php b/lang/el/lang.php deleted file mode 100644 index d266b40d..00000000 --- a/lang/el/lang.php +++ /dev/null @@ -1,113 +0,0 @@ - [ - 'name' => 'Σελίδες', - 'description' => 'Σελίδες και Μενού.', - ], - 'page' => [ - 'menu_label' => 'Σελίδες', - 'template_title' => '%s Σελίδες', - 'delete_confirmation' => 'Θέλετε πραγματικά να διαγράψετε τις επιλεγμένες σελίδες; Αυτό θα διαγράψει και τις υποσελίδες, εάν υπάρχουν.', - 'no_records' => 'Δεν βρέθηκαν σελίδες', - 'delete_confirm_single' => 'Θέλετε πραγματικά να διαγράψετε αυτή την σελίδα; Αυτό θα διαγράψει και τις υποσελίδες, εάν υπάρχουν.', - 'new' => 'Νέα σελίδα', - 'add_subpage' => 'Προσθήκη υποσελίδας', - 'invalid_url' => 'Μή έγκυρη μορφή URL. Το URL πρέπει να αρχίζει με το σύμβολο μπροστινής καθέτου και μπορεί να περιέχει αριθμητικά ψηφία, Λατινικούς χαρακτήρες και τα ακόλουθα σύμβολα: _-/.', - 'url_not_unique' => 'Αυτό το URL χρησιμοποιήται ήδη από άλλη σελίδα.', - 'layout' => 'Σχέδιο', - 'layouts_not_found' => 'Δεν βρέθηκαν σχέδια', - 'saved' => 'Η σελίδα αποθηκεύτηκε επιτυχώς.', - 'tab' => 'Σελίδες', - 'manage_pages' => 'Διαχείριση στατικών σελίδων', - 'manage_menus' => 'Διαχείριση στατικών μενού', - 'access_snippets' => 'Πρόσβαση στα αποσπάσματα', - 'manage_content' => 'Διαχείριση στατικού περιεχομένου', - ], - 'menu' => [ - 'menu_label' => 'Μενού', - 'delete_confirmation' => 'Θέλετε πραγματικά να διαγράψετε τα επιλεγμένα μενού;', - 'no_records' => 'Δεν βρέθηκανε μενού', - 'new' => 'Νέο μενού', - 'new_name' => 'Νέο μενού', - 'new_code' => 'new-menu', - 'delete_confirm_single' => 'Θέλετε πραγματικά να διαγράψετε αυτό το μενού;', - 'saved' => 'Αυτό το μενού αποθηκεύτηκε επιτυχώς.', - 'name' => 'Όνομα', - 'code' => 'Κωδικός', - 'items' => 'Στοιχεία μενού', - 'add_subitem' => 'Προσθήκη υποστοιχείου', - 'code_required' => 'Ο Κωδικός είναι απαραίτητος', - 'invalid_code' => 'Μή έγκυρη μορφή Κωδικού. Ο Κωδικός μπορεί να περιέχει αριθμητικά ψηφία, Λατινικούς χαρακτήρες και τα ακόλουθα σύμβολα: _-', - ], - 'menuitem' => [ - 'title' => 'Τίτλος', - 'editor_title' => 'Επεξεργασία στοιχείου μενού', - 'type' => 'Τύπος', - 'allow_nested_items' => 'Να επιτρέπονται ένθετα στοιχεία', - 'allow_nested_items_comment' => 'Τα ένθετα στοιχεία μπορούν να δημιουργηθούν δυναμικά από στατικές σελίδες και κάποιους άλλους τύπους στοιχείων', - 'url' => 'URL', - 'reference' => 'Αναφορά', - 'search_placeholder' => 'Αναζήτηση αναφορών...', - 'title_required' => 'Ο Τίτλος είναι απαραίτητος', - 'unknown_type' => 'Άγνωστος τύπος στοιχείου μενού', - 'unnamed' => 'Στοιχείο μενού χωρίς όνομα', - 'add_item' => 'Προσθήκη Στοιχείου', - 'new_item' => 'Νέο στοιχείο μενού', - 'replace' => 'Αντικατάσταση του στοιχείου με τα παραγμένα υποστοιχεία', - 'replace_comment' => 'Χρησιμοποιήστε αυτό το πλαίσιο για να ωθήσετε τα παραγμένα στοιχεία μενού στο ίδιο επίπεδο με αυτό το στοιχείο. Το ίδιο το στοιχείο θα γίνει κρυφό.', - 'cms_page' => 'Σελίδα CMS', - 'cms_page_comment' => 'Επιλέξτε σελίδα που θα ανοίγει όταν αυτό το μενού πατηθεί.', - 'reference_required' => 'Η αναφορά του στοιχείου μενού είναι απαραίτητη.', - 'url_required' => 'Το URL είναι απαραίτητο', - 'cms_page_required' => 'Παρακαλώ διαλέξτε μία σελίδα CMS', - 'code' => 'Κωδικός', - 'code_comment' => 'Εισάγετε το κωδικό του μενού εάν θέλετε να έχετε πρόσβαση μέσω του API.', - 'static_page' => 'Στατική σελίδα', - 'all_static_pages' => 'Όλες οι Στατικές Σελίδες', - ], - 'content' => [ - 'menu_label' => 'Περιεχόμενο', - 'cant_save_to_dir' => 'Η αποθήκευση των αρχείων περιεχομένου στο φάκελο στατικών σελίδων δεν επιτρέπεται.', - ], - 'sidebar' => [ - 'add' => 'Προσθήκη', - ], - 'object' => [ - 'invalid_type' => 'Άγνωστος τύπος αντικειμένου', - 'not_found' => 'Το ζητούμενο αντικείμενο δεν βρέθηκε.', - ], - 'editor' => [ - 'title' => 'Τίτλος', - 'new_title' => 'Νέος τίτλος σελίδας', - 'content' => 'Περιεχόμενο', - 'url' => 'URL', - 'filename' => 'Όνομα αρχείου', - 'layout' => 'Σχέδιο', - 'description' => 'Περιγραφή', - 'preview' => 'Προεπισκόπηση', - 'enter_fullscreen' => 'Πλήρης οθόνη', - 'exit_fullscreen' => 'Έξοδος πλήρους οθόνης', - 'hidden' => 'Κρυφό', - 'hidden_comment' => 'Κρυφές σελίδες είναι προσβάσιμες μόνο στους συνδεδεμένους χρήστες back-end.', - 'navigation_hidden' => 'Απόκρυψη στην πλοήγηση', - 'navigation_hidden_comment' => 'Επιλέξτε αυτό το πλαίσιο για να κρύψετε αυτή την σελίδα από αυτοματοποιημένα μενού και breadcrumbs.', - ], - 'snippet' => [ - 'menu_label' => 'Αποσπάσματα', - ], - 'component' => [ - 'static_page_name' => 'Στατική σελίδα', - 'static_page_description' => 'Παράγει μία στατική σελίδα ενσωματωμένη σε ένα σχέδιο CMS.', - 'static_page_use_content_name' => 'Χρησιμοποιήστε το πεδίο περιεχομένου σελίδας', - 'static_page_use_content_description' => 'Εάν δεν είναι επιλεγμένο, η περιοχή περιεχομένου δεν θα εμφανίζεται όταν επεξεργάζεται η στατική σελίδα. Το περιεχόμενο της σελίδας θα ορίζεται αποκλειστικά μέσω αντικαταστατών και μεταβλητών.', - 'static_page_default_name' => 'Προεπιλεγμένο σχέδιο', - 'static_page_default_description' => 'Ορίζει αυτό το σχέδιο σαν το προεπιλεγμένο για καινούργιες σελίδες', - 'static_page_child_layout_name' => 'Σχέδιο υποσελίδων', - 'static_page_child_layout_description' => 'Το προεπιλεγμένο σχέδιο για καινούργιες υποσελίδες', - 'static_menu_name' => 'Στατικό μενού', - 'static_menu_description' => 'Παράγει ένα μενού σε ένα σχέδιο CMS.', - 'static_menu_code_name' => 'Μενού', - 'static_menu_code_description' => 'Ορίστε ένα κωδικό του μενού που το Δομικό Στοιχείο θα πρέπει να εξάγει.', - 'static_breadcrumbs_name' => 'Στατικά breadcrumbs', - 'static_breadcrumbs_description' => 'Παράγει breadcrumbs για μία στατική σελίδα.', - ], -]; diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 00000000..6c7f480c --- /dev/null +++ b/lang/en.json @@ -0,0 +1,75 @@ +{ + "Pages": "Pages", + "Pages & menus features.": "Pages & menus features.", + "Manage static pages": "Manage static pages", + "Manage static menus": "Manage static menus", + "Manage static content": "Manage static content", + "Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/.": "Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/.", + "This URL is already used by another page.": "This URL is already used by another page.", + "Layouts not found": "Layouts not found", + "The Code is required": "The Code is required", + "Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-": "Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-", + "Static page": "Static page", + "All static pages": "All static pages", + "Static menu": "Static menu", + "Static breadcrumbs": "Static breadcrumbs", + "Child pages": "Child pages", + "Outputs a static page in a CMS layout.": "Outputs a static page in a CMS layout.", + "Use page content field": "Use page content field", + "If unchecked, the content section will not appear when editing the static page. Page content will be determined solely through placeholders and variables.": "If unchecked, the content section will not appear when editing the static page. Page content will be determined solely through placeholders and variables.", + "Default layout": "Default layout", + "Defines this layout as the default for new pages": "Defines this layout as the default for new pages", + "Subpage layout": "Subpage layout", + "The layout to use as the default for any new subpages": "The layout to use as the default for any new subpages", + "Outputs a menu in a CMS layout.": "Outputs a menu in a CMS layout.", + "Menu": "Menu", + "Specify a code of the menu the component should output.": "Specify a code of the menu the component should output.", + "Outputs breadcrumbs for a static page.": "Outputs breadcrumbs for a static page.", + "Displays a list of child pages for the current page": "Displays a list of child pages for the current page", + "Static Pages": "Static Pages", + "Menus": "Menus", + "Content": "Content", + "Add": "Add", + "Refresh": "Refresh", + "Page": "Page", + "Content block": "Content block", + "New page": "New page", + "New page title": "New page title", + "New menu": "New menu", + "New content block": "New content block", + "Title": "Title", + "URL": "URL", + "File Name": "File Name", + "Layout": "Layout", + "Hidden": "Hidden", + "Hide in navigation": "Hide in navigation", + "Description": "Description", + "Name": "Name", + "Code": "Code", + "The Title is required.": "The Title is required.", + "The URL is required.": "The URL is required.", + "The File Name is required.": "The File Name is required.", + "The Name is required.": "The Name is required.", + "The Code is required.": "The Code is required.", + "Error loading page": "Error loading page", + "Error loading menu": "Error loading menu", + "Error loading content block": "Error loading content block", + "Add item": "Add item", + "New menu item": "New menu item", + "Untitled": "Untitled", + "Up": "Up", + "Down": "Down", + "Indent": "Indent", + "Outdent": "Outdent", + "Delete": "Delete", + "No menu items yet. Use \"Add item\" in the toolbar.": "No menu items yet. Use \"Add item\" in the toolbar.", + "Select a menu item to edit, or add a new one.": "Select a menu item to edit, or add a new one.", + "Custom Fields": "Custom Fields", + "Search all references...": "Search all references...", + "Edit Menu Item": "Edit Menu Item", + "Move up": "Move up", + "Move down": "Move down", + "Apply": "Apply", + "Cancel": "Cancel", + "Error loading page": "Error loading page" +} diff --git a/lang/en/lang.php b/lang/en/lang.php deleted file mode 100644 index 2664c7f2..00000000 --- a/lang/en/lang.php +++ /dev/null @@ -1,130 +0,0 @@ - [ - 'name' => 'Pages', - 'description' => 'Pages & menus features.', - ], - 'page' => [ - 'menu_label' => 'Pages', - 'template_title' => '%s Pages', - 'delete_confirmation' => 'Do you really want to delete selected pages? This will also delete the subpages, if any.', - 'no_records' => 'No pages found', - 'delete_confirm_single' => 'Do you really want delete this page? This will also delete the subpages, if any.', - 'new' => 'New page', - 'add_subpage' => 'Add subpage', - 'invalid_url' => 'Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/.', - 'url_not_unique' => 'This URL is already used by another page.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Layouts not found', - 'saved' => 'The page has been successfully saved.', - 'tab' => 'Pages', - 'manage_pages' => 'Manage static pages', - 'manage_menus' => 'Manage static menus', - 'access_snippets' => 'Access snippets', - 'manage_content' => 'Manage static content', - ], - 'menu' => [ - 'menu_label' => 'Menus', - 'delete_confirmation' => 'Do you really want to delete selected menus?', - 'no_records' => 'No menus found', - 'new' => 'New menu', - 'new_name' => 'New menu', - 'new_code' => 'new-menu', - 'delete_confirm_single' => 'Do you really want delete this menu?', - 'saved' => 'The menu has been successfully saved.', - 'name' => 'Name', - 'code' => 'Code', - 'items' => 'Menu items', - 'add_subitem' => 'Add subitem', - 'code_required' => 'The Code is required', - 'invalid_code' => 'Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-', - ], - 'menuitem' => [ - 'title' => 'Title', - 'editor_title' => 'Edit Menu Item', - 'type' => 'Type', - 'allow_nested_items' => 'Allow nested items', - 'allow_nested_items_comment' => 'Nested items could be generated dynamically by static page and some other item types', - 'url' => 'URL', - 'reference' => 'Reference', - 'search_placeholder' => 'Search all references...', - 'title_required' => 'The Title is required', - 'unknown_type' => 'Unknown menu item type', - 'unnamed' => 'Unnamed menu item', - 'add_item' => 'Add Item', - 'new_item' => 'New menu item', - 'replace' => 'Replace this item with its generated children', - 'replace_comment' => 'Use this checkbox to push generated menu items to the same level with this item. This item itself will be hidden.', - 'cms_page' => 'CMS Page', - 'cms_page_comment' => 'Select a page to open when the menu item is clicked.', - 'reference_required' => 'The menu item reference is required.', - 'url_required' => 'The URL is required', - 'cms_page_required' => 'Please select a CMS page', - 'display_tab' => 'Display', - 'hidden' => 'Hidden', - 'hidden_comment' => 'Hide this menu item from appearing on the front-end.', - 'attributes_tab' => 'Attributes', - 'code' => 'Code', - 'code_comment' => 'Enter the menu item code if you want to access it with the API.', - 'css_class' => 'CSS Class', - 'css_class_comment' => 'Enter a CSS class name to give this menu item a custom appearance.', - 'external_link' => 'External link', - 'external_link_comment' => 'Open links for this menu item in a new window.', - 'static_page' => 'Static Page', - 'all_static_pages' => 'All Static Pages', - ], - 'content' => [ - 'menu_label' => 'Content', - 'saved' => 'The content has been successfully saved.', - 'cant_save_to_dir' => 'Saving content files to the static-pages directory is not allowed.', - ], - 'template' => [ - 'order_by' => 'Order by', - 'no_list_records' => 'No records found', - 'delete_confirm' => 'Delete selected templates?', - ], - 'sidebar' => [ - 'add' => 'Add', - ], - 'object' => [ - 'invalid_type' => 'Unknown object type', - 'unauthorized_type' => 'You are not authorized to manage :type objects', - 'not_found' => 'The requested object was not found.', - ], - 'editor' => [ - 'title' => 'Title', - 'new_title' => 'New page title', - 'content' => 'Content', - 'url' => 'URL', - 'filename' => 'File Name', - 'layout' => 'Layout', - 'description' => 'Description', - 'preview' => 'Preview', - 'enter_fullscreen' => 'Enter fullscreen mode', - 'exit_fullscreen' => 'Exit fullscreen mode', - 'hidden' => 'Hidden', - 'hidden_comment' => 'Hidden pages are accessible only by logged-in back-end users.', - 'navigation_hidden' => 'Hide in navigation', - 'navigation_hidden_comment' => 'Check this box to hide this page from automatically generated menus and breadcrumbs.', - ], - 'snippet' => [ - 'menu_label' => 'Snippets', - ], - 'component' => [ - 'static_page_name' => 'Static page', - 'static_page_description' => 'Outputs a static page in a CMS layout.', - 'static_page_use_content_name' => 'Use page content field', - 'static_page_use_content_description' => 'If unchecked, the content section will not appear when editing the static page. Page content will be determined solely through placeholders and variables.', - 'static_page_default_name' => 'Default layout', - 'static_page_default_description' => 'Defines this layout as the default for new pages', - 'static_page_child_layout_name' => 'Subpage layout', - 'static_page_child_layout_description' => 'The layout to use as the default for any new subpages', - 'static_menu_name' => 'Static menu', - 'static_menu_description' => 'Outputs a menu in a CMS layout.', - 'static_menu_code_name' => 'Menu', - 'static_menu_code_description' => 'Specify a code of the menu the component should output.', - 'static_breadcrumbs_name' => 'Static breadcrumbs', - 'static_breadcrumbs_description' => 'Outputs breadcrumbs for a static page.', - 'child_pages_name' => 'Child pages', - 'child_pages_description' => 'Displays a list of child pages for the current page', - ], -]; diff --git a/lang/es/lang.php b/lang/es/lang.php deleted file mode 100644 index 88511983..00000000 --- a/lang/es/lang.php +++ /dev/null @@ -1,94 +0,0 @@ - [ - 'name' => 'Páginas', - 'description' => 'Páginas & menus', - ], - 'page' => [ - 'menu_label' => 'Páginas', - 'template_title' => '%s Páginas', - 'delete_confirmation' => 'Estas seguro de querer borrar las páginas seleccionadas? Esto también borrará las sub-páginas que existan.', - 'no_records' => 'No se ha encontrado ninguna página', - 'delete_confirm_single' => 'Estas seguro de querer borrar esta página? Esto también borrará las sub-páginas que existan.', - 'new' => 'Nueva página', - 'add_subpage' => 'Añadir sub-página', - 'invalid_url' => 'Formato de URL no válido. La URL debería comenzar por una barra (\'/\'). Puede contener letras, números, y los siguientes símbolos _ - / ', - 'url_not_unique' => 'Esta URL ya está siendo utilizada por otra página.', - 'layout' => 'Plantilla', - 'layouts_not_found' => 'No se han encontrado plantillas', - 'saved' => 'La página se ha guardado correctamente.', - 'tab' => 'Páginas', - 'manage_pages' => 'Administrar páginas', - 'manage_menus' => 'Administrar menús', - 'access_snippets' => 'Acceder a fragmentos', - 'manage_content' => 'Administrar contenidos', - ], - 'menu' => [ - 'menu_label' => 'Menus', - 'delete_confirmation' => 'Estas seguro de querer borrar los menus seleccionados?', - 'no_records' => 'No se han encontrado elementos.', - 'new' => 'Nuevo menu', - 'new_name' => 'Nuevo menu', - 'new_code' => 'nuevo-menu', - 'delete_confirm_single' => 'Estas seguro de querer borrar este menu?', - 'saved' => 'El menú se ha guardado correctamente.', - 'name' => 'Nombre', - 'code' => 'Código', - 'items' => 'Elementos del menu', - 'add_subitem' => 'Añadir sub-elemento', - 'code_required' => 'El código es obligatorio', - 'invalid_code' => 'El formato del código no es válido. Puede contener letras, números y los siguientes símbolos: _ - ', - ], - 'menuitem' => [ - 'title' => 'Título', - 'editor_title' => 'Editar elemento del menu', - 'type' => 'Tipo', - 'allow_nested_items' => 'Permitir elementos anidados', - 'allow_nested_items_comment' => 'Los elementos anidados se pueden generar automáticamente mediante las páginas y otros tipos de elementos.', - 'url' => 'URL', - 'reference' => 'Referencia', - 'title_required' => 'El título es obligatorio', - 'unknown_type' => 'Este tipo de elemento del menú es desconocido.', - 'unnamed' => 'Elemento del menú sin nombre', - 'add_item' => 'Añadir elemento', - 'new_item' => 'Nuevo elemento', - 'replace' => 'Sustituye este elemento por los sub-elementos que contenga.', - 'replace_comment' => 'Marca esta casilla sustituir este elemento por los sub-elementos que contenga. El elemento proncipal quedará oculto.', - 'cms_page' => 'Página del CMS', - 'cms_page_comment' => 'Selecciona una página a la que enlazar cuando se haga click en este elemento del menu.', - 'reference_required' => 'La referencia al elemento del menú es obligatoria.', - 'url_required' => 'La URL es obligatoria', - 'cms_page_required' => 'Selecciona una página del CMS', - 'code' => 'Código', - 'code_comment' => 'Introduce el código del elemento para acceder mediante la API.', - ], - 'content' => [ - 'menu_label' => 'Contenido', - 'cant_save_to_dir' => 'No está permitido guardar archivos de contenido en el directorio de las páginas.', - ], - 'sidebar' => [ - 'add' => 'Añadir', - ], - 'object' => [ - 'invalid_type' => 'Tipo de objeto desconocido', - 'not_found' => 'No se ha encontrado el objeto solicitado.', - ], - 'editor' => [ - 'title' => 'Título', - 'new_title' => 'Título de la nueva página', - 'content' => 'Contenido', - 'url' => 'URL', - 'filename' => 'Nombre de archivo', - 'layout' => 'Plantilla', - 'description' => 'Descripción', - 'preview' => 'Vista previa', - 'enter_fullscreen' => 'Entrar en modo de pantalla completa', - 'exit_fullscreen' => 'Salir del modo de pantalla completa', - 'hidden' => 'Oculto', - 'hidden_comment' => 'Las páginas ocultas solo son visibles para los administradores que hayan iniciado sesión.', - 'navigation_hidden' => 'No mostrar en el menu', - 'navigation_hidden_comment' => 'Marca esta casilla para ocultar esta página en los menus generados automáticamente.', - ], - 'snippet' => [ - 'menu_label' => 'Fragmentos', - ], -]; diff --git a/lang/fa/lang.php b/lang/fa/lang.php deleted file mode 100644 index ce72c3cb..00000000 --- a/lang/fa/lang.php +++ /dev/null @@ -1,113 +0,0 @@ - [ - 'name' => 'صفحات', - 'description' => 'مدیریت صفحات و فهرست ها', - ], - 'page' => [ - 'menu_label' => 'صفحات', - 'template_title' => 'صفحات %s', - 'delete_confirmation' => 'آیا از حذف صفحات انتخاب شده اطمینان دارید؟ اگر صفحات دارای زیر صفحه باشند آنها نیز حذف خواهند شد.', - 'no_records' => 'صفحه ای یافت نشد', - 'delete_confirm_single' => 'آیا از حذف این صفحه اطمینان دارید؟ اگر این صفحه دارای زیر مجموعه باشد آنها نیز حذف خواهند شد.', - 'new' => 'صفحه ی جدید', - 'add_subpage' => 'افزودن زیر مجموعه', - 'invalid_url' => 'قالب آدرس نا معتبر است. آدرس باید با اسلش شروع شود و میتواند شامل حروف لاتین، حروف فارسی، اعداد و این کاراکتر ها باشد: _-/.', - 'url_not_unique' => 'این آدرس توسط صفحه ی دیگری استفاده شده است.', - 'layout' => 'طرح بندی', - 'layouts_not_found' => 'طرح بندی برای صفحات استاتیک یافت نشد.', - 'saved' => 'صفحه با موفقیت ذخیره شد.', - 'tab' => 'صفحات', - 'manage_pages' => 'مدیریت صفحات استاتیک', - 'manage_menus' => 'مدیریت فهرست های استاتیک', - 'access_snippets' => 'دسترسی به تکه کد ها', - 'manage_content' => 'مدیریت محتوی استاتیک', - ], - 'menu' => [ - 'menu_label' => 'فهرست ها', - 'delete_confirmation' => 'آیا از حذف فهرست انتخاب شده اطمینان دارید؟', - 'no_records' => 'موردی یافت نشد', - 'new' => 'فهرست جدید', - 'new_name' => 'فهرست جدید', - 'new_code' => 'new-menu', - 'delete_confirm_single' => 'آیا از حذف این فهرست اطمینان دارید؟', - 'saved' => 'فهرست با موفقیت ذخیره شد.', - 'name' => 'نام', - 'code' => 'کد', - 'items' => 'موارد فهرست', - 'add_subitem' => 'افزودن زیر فهرست', - 'code_required' => 'وارد کردن کد اجباریست', - 'invalid_code' => 'قالب کد نا معتبر است. کد میتواند شامل اعداد، حروف لاتین و این کاراکتر ها باشد: _-', - ], - 'menuitem' => [ - 'title' => 'عنوان', - 'editor_title' => 'ویرایش فهرست', - 'type' => 'نوع', - 'allow_nested_items' => 'استفاده از موارد تو در تو', - 'allow_nested_items_comment' => 'موارد تو در تو به صورت خودکار توسط صفحات استاتیک و برخی از دیگر موارد ایجاد می شوند', - 'url' => 'آدرس', - 'reference' => 'مرجع', - 'search_placeholder' => 'جستجوی همه موارد...', - 'title_required' => 'وارد کردن عنوان اجباریست', - 'unknown_type' => 'نوع نامشخص فهرست', - 'unnamed' => 'فهرست بدون نام', - 'add_item' => 'افزودن فهرست', - 'new_item' => 'مورد جدید برای فهرست', - 'replace' => 'جایگرینی این مورد با زیر مورد های ایجاد شده', - 'replace_comment' => 'اگر میخواهید زیر فهرست های ایجاد شده هم سطح با این مورد قرار بگیرند این گزینه را فعال نمایید. خود فهرست بصورت خودکار مخفی خواهد شد.', - 'cms_page' => 'صفحه ی مدیریت محتوی', - 'cms_page_comment' => 'صفحه ای را که میخواهید به هنگام انتخاب این فهرست باز شود را انتخاب نمایید.', - 'reference_required' => 'وارد کردن مرجع برای فهرست الزامیست.', - 'url_required' => 'وارد کردن آدرس الزامیست', - 'cms_page_required' => 'لطفا یک صفحه را انتخاب کنید', - 'code' => 'کد', - 'code_comment' => 'اگر میخواهید از طریق کد ها به این مورد از فهرست دسترسی پیدا کنید کد آن را وارد نمایید.', - 'static_page' => 'صفحات استاتیک', - 'all_static_pages' => 'تمام صفحات استاتیک', - ], - 'content' => [ - 'menu_label' => 'محتوی', - 'cant_save_to_dir' => 'مجوز ذخیره ی داده ها در پوشه ی صفحات استاتسک وجود ندارد.', - ], - 'sidebar' => [ - 'add' => 'افزودن', - ], - 'object' => [ - 'invalid_type' => 'نوع شیء نا مشخص است', - 'not_found' => 'شیء درخواستی یافت نشد.', - ], - 'editor' => [ - 'title' => 'عنوان', - 'new_title' => 'عنوان صفحه ی جدید', - 'content' => 'محتوی', - 'url' => 'آدرس', - 'filename' => 'نام فایل', - 'layout' => 'طرح بندی', - 'description' => 'توضیحات', - 'preview' => 'پیش نمایش', - 'enter_fullscreen' => 'حالت تمام صفحه', - 'exit_fullscreen' => 'خروج از حالت تمام صفحه', - 'hidden' => 'مخفی', - 'hidden_comment' => 'صفحات مخفی توسط کاربران وارد شده به سایت قابل دسترس می باشند.', - 'navigation_hidden' => 'مخفی کردن در فهرست', - 'navigation_hidden_comment' => 'اگر میخواهید صفحه مورد نظر در فهرست هایی که خودکار ایجاد می شوند و یا نشان گرهای صفحه دیده نشوند این گزینه را انتخاب نمایید.', - ], - 'snippet' => [ - 'menu_label' => 'تکه کدها', - ], - 'component' => [ - 'static_page_name' => 'صفحات استاتیک', - 'static_page_description' => 'نمایش یک صفحه استاتیک در طرح بندی.', - 'static_page_use_content_name' => 'استفاده از گرینه ها در محتوی.', - 'static_page_use_content_description' => 'اگر غیر فعال باشد، فیلد ها به هنگام ویرایش صفحات استاتیک در بخش محتوی نمایش داده نخواهند شد. محتوی صفحه با استفاده از متغییر های تعریف شده قابل کنتل می باشند.', - 'static_page_default_name' => 'طرح بندی پیش فرض', - 'static_page_default_description' => 'این طرح بندی به عنوان طرح بندی پیشفرض به هنگام ایجاد صفحه جدید در نظر گرفته شود؟', - 'static_page_child_layout_name' => 'طرح بندی صفحات زیرمجموعه', - 'static_page_child_layout_description' => 'این طرح بندی به عنوان طرح بندی تمام صفحات زیر مجموعه در نظر گرفنه شود؟', - 'static_menu_name' => 'فهرست استاتیک', - 'static_menu_description' => 'نمایش فهرست استاتیک در طرح بندی.', - 'static_menu_code_name' => 'فهرست', - 'static_menu_code_description' => 'کد فهرستی را که میخواهید به نمایش درآید انتخاب نمایید.', - 'static_breadcrumbs_name' => 'نشان گرها', - 'static_breadcrumbs_description' => 'نمایش نشان گرهای صفحه.', - ], -]; diff --git a/lang/fi/lang.php b/lang/fi/lang.php deleted file mode 100644 index ef64dbf1..00000000 --- a/lang/fi/lang.php +++ /dev/null @@ -1,151 +0,0 @@ - [ - 'name' => 'Sivut', - 'description' => 'Sivu- ja valikko-ominaisuudet.', - ], - 'page' => [ - 'menu_label' => 'Sivut', - 'template_title' => '%s Sivut', - 'delete_confirmation' => 'Haluatko varmasti poistaa valitut sivut? Tämä poistaa myös alasivut, jos sellaisia on.', - 'no_records' => 'Sivuja ei löytynyt', - 'delete_confirm_single' => 'Haluatko varmasti poistaa tämän sivun? Tämä poistaa myös alasivut, jos sellaisia on.', - 'new' => 'Uusi sivu', - 'add_subpage' => 'Lisää alasivu', - 'invalid_url' => 'Kelvoton URL formaatti. URL pitäisi alkaa kautta -merkillä ja voi sisältää kokonaislukuja, latinalaisia kirjamia, ja seuraavia merkkejä: _-/.', - 'url_not_unique' => 'Tämä URL on toisen sivun käyttämä.', - 'layout' => 'Ulkoasu', - 'layouts_not_found' => 'Ulkoasuja ei löytynyt', - 'saved' => 'Sivu on tallennettu onnistuneesti.', - 'tab' => 'Sivut', - 'manage_pages' => 'Hallitse staattisia sivuja', - 'manage_menus' => 'Hallitse staattisia valikkoja', - 'access_snippets' => 'Hallitse koodinpätkiä', - 'manage_content' => 'Hallitse staattista sisältöä', - ], - 'menu' => [ - 'menu_label' => 'Valikot', - 'delete_confirmation' => 'Haluatko varmasti poista valitut valikot?', - 'no_records' => 'Vakujjiha ei löytynyt', - 'new' => 'Uusi valikko', - 'new_name' => 'Uusi valikko', - 'new_code' => 'uusi-valikko', - 'delete_confirm_single' => 'Haluatko varmasti poistaa tämän valikon?', - 'saved' => 'Tämä valikko on tallennettu onnistuneesti.', - 'name' => 'Nimi', - 'code' => 'Koodi', - 'items' => 'Valikon kohteet', - 'add_subitem' => 'Lisää alakohde', - 'code_required' => 'Koodi on vaadittu', - 'invalid_code' => 'Koodilla on kelvoton formaatti. Koodi voi sisältää kokonaislukuja, latinalaisia kirjamia, ja seuraavia merkkejä: _-', - ], - 'menuitem' => [ - 'title' => 'Otsikko', - 'editor_title' => 'Muokkaa valikkokohdetta', - 'type' => 'Tyyppi', - 'allow_nested_items' => 'Salli sisäkkäiset kohteet', - 'allow_nested_items_comment' => 'Sisäkkäiset kohteet staattisissa sivuissa ja muissa kohdetyypeissä voidaan generoida dynaamisesti', - 'url' => 'URL', - 'reference' => 'Viite', - 'search_placeholder' => 'Hae kaikista viitteistä...', - 'title_required' => 'Otsikko on vaadittu', - 'unknown_type' => 'Tuntematon valikkokohteen tyyppi', - 'unnamed' => 'Nimeämätön valikkokohde', - 'add_item' => 'Lisää Kohde', - 'new_item' => 'Uusi valikkokohde', - 'replace' => 'Korvaa valikko sen generoimilla alikohteilla', - 'replace_comment' => 'Käytä tätä valintaruutua työntääksesi valikon kohteet samalle tasolle tämän kohteen kanssa. Tämä kohde itsessään on piilotettu.', - 'cms_page' => 'CMS sivu', - 'cms_page_comment' => 'Valitse sivu joka avataan, kun valikkokohtaa napsautetaan.', - 'reference_required' => 'Valikkokohteen viite on vaadittu.', - 'url_required' => 'URL on vaadittu', - 'cms_page_required' => 'Valitse CMS sivu', - 'display_tab' => 'Näkyvyys', - 'hidden' => 'Piilotettu', - 'hidden_comment' => 'Piilota tämän valikon kohde näkyvistä käyttäjän näkymässä.', - 'attributes_tab' => 'Attribuutti', - 'code' => 'Koodi', - 'code_comment' => 'Syötä valikkokohten koodi jos haluat käyttää sitä API:n kanssa.', - 'css_class' => 'CSS-luokka', - 'css_class_comment' => 'Anna tämän valikon kohteelle oma CSS-luokka ulkoasua varten.', - 'external_link' => 'Ulkoinen linkki', - 'external_link_comment' => 'Avaa linkki tämän valikon kohteesta uudessa ikkunassa.', - 'static_page' => 'Staattinen sivu', - 'all_static_pages' => 'Kaikki staattiset sivut' - ], - 'content' => [ - 'menu_label' => 'Sisältö', - 'saved' => 'Sisältö tallennettu onnistuneesti.', - 'cant_save_to_dir' => 'Sisältötiedostojen tallentaminen staatisen-sivujen hakemistoon ei ole sallittua.', - ], - 'sidebar' => [ - 'add' => 'Lisää', - 'search' => 'Hae...' - ], - 'object' => [ - 'invalid_type' => 'Tuntematon kohdetyyppi', - 'unauthorized_type' => 'Sinulla ei ole oikeuksia muokata kohdetta :type', - 'not_found' => 'Pyydettyä kohdetta ei löytynyt.', - ], - 'editor' => [ - 'title' => 'Otsikko', - 'new_title' => 'Uuden sivun otsikko', - 'content' => 'Sisältö', - 'url' => 'URL', - 'filename' => 'Tiedostonimi', - 'layout' => 'Ulkoasu', - 'description' => 'Kuvaus', - 'preview' => 'Esikatsele', - 'enter_fullscreen' => 'Kokoruudun tila', - 'exit_fullscreen' => 'Poistu kokoruudun tilasta', - 'hidden' => 'Piilotettu', - 'hidden_comment' => 'Piilotetut sivut ovat saatavilla ainoastaan hallintaan kirjautuneille.', - 'navigation_hidden' => 'Piilota navigaatiosta', - 'navigation_hidden_comment' => 'Käytä tätä valintaruutua piilottaaksesi tämä sivu automaattisesti generoiduista valikoista ja leivänmuruista.', - ], - 'snippet' => [ - 'partialtab' => 'Osat', - 'settings_popup_title' => 'Staattisten sivujen koodinpätkä', - 'code' => 'Osan koodi', - 'code_comment' => 'Syötä koodi, jotta tämä osio on käytettävissä Staattisten sivujen -lisäosassa.', - 'code_required' => 'Ole hyvä ja lisää koodin pätkä', - 'name' => 'Nimi', - 'name_comment' => 'Nimi näkyy osiolistassa Staattisten sivujen -sivupalkissa ja sivuilla kun osa on lisätty.', - 'name_required' => 'Pätkällä on oltava nimi', - 'no_records' => 'Osia ei löydy', - 'menu_label' => 'Osat', - 'properties' => 'Koodin pätkän ominaisuudet', - 'column_property' => 'Ominaisuuden otsikko', - 'title_required' => 'Ole hyvä ja lisää ominaisuuden otsikko', - 'type_required' => 'Valitse ominaisuuden tyyppi', - 'property_required' => 'Ominaisuuden nimi on pakollinen', - 'column_type' => 'Tyyppi', - 'column_type_placeholder' => 'Valitse', - 'column_code' => 'Koodi', - 'column_default' => 'Oletus', - 'column_options' => 'Vaihtoehdot', - 'column_type_string' => 'Merkkijono', - 'column_type_checkbox' => 'Valintaruutu', - 'column_type_dropdown' => 'Alasvetovalikko', - 'not_found' => 'Osaa pyydetyllä koodilla :code ei löytynyt teemasta.', - 'property_format_error' => 'Ominaisuuden koodin tulisi alkaa latinalaisella kirjaimella ja voi sisältää ainoastaan latinalaisia merkkejä ja kokonaislukuja', - 'invalid_option_key' => 'Kelvoton alasvetovalikon vaihtoehtoavain :key. Vaihtoehtojen avaimet voivat sisältää ainoastaan kokonaislukuja, latinalaisia merkkejä, ja merkkejä _ ja -', - ], - 'component' => [ - 'static_page_name' => 'Staattinen sivu', - 'static_page_description' => 'Näyttää staattisen sivun CMS ulkoasussa.', - 'static_page_use_content_name' => 'Käytä sivun sisältökenttää Use page content field', - 'static_page_use_content_description' => 'Jos valitsematta, sisältö -kohta ei tule näkyviin staattista sivua muokattaessa. Sivun sisältö määritetään vain paikkamerkkien ja muuttujien kautta.', - 'static_page_default_name' => 'Oletusulkoasu', - 'static_page_default_description' => 'Määrittelee tämän ulkoasun oletukseksi uusille sivuille', - 'static_page_child_layout_name' => 'Alasivun ulkoasu', - 'static_page_child_layout_description' => 'Oletusulkoasu kaikille uusille alasivuille', - 'static_menu_name' => 'Staattinen valikko', - 'static_menu_description' => 'Näyttää valikon CMS ulkoasussa.', - 'static_menu_code_name' => 'Valikko', - 'static_menu_code_description' => 'Määritä valikon koodi joka pitäisi näyttää.', - 'static_breadcrumbs_name' => 'Staattinen murupolku', - 'static_breadcrumbs_description' => 'Näyttää murupolun staattisella sivulla.', - 'child_pages_name' => 'Alasivut', - 'child_pages_description' => 'Näyttää listan nykyisen sivun alasivuista', - ] -]; diff --git a/lang/fr/lang.php b/lang/fr/lang.php deleted file mode 100644 index 2d9b8af4..00000000 --- a/lang/fr/lang.php +++ /dev/null @@ -1,123 +0,0 @@ - [ - 'name' => 'Pages', - 'description' => 'Fonctionnalités de pages et menus statiques.', - ], - 'page' => [ - 'menu_label' => 'Pages', - 'template_title' => '%s Pages', - 'delete_confirmation' => 'Confirmez-vous la suppression des pages sélectionnées ? Les sous-pages seront également supprimées.', - 'no_records' => 'Aucune page trouvée', - 'delete_confirm_single' => 'Confirmez-vous la suppression de cette page ? Les sous-pages seront également supprimées.', - 'new' => 'Nouvelle page', - 'add_subpage' => 'Ajouter une sous-page', - 'invalid_url' => 'Le format d’URL est invalide. L’URL doit commencer par un / et peut contenir des chiffres, des lettres et les symboles suivants : _-/.', - 'url_not_unique' => 'Cette URL est déjà utilisée par une autre page.', - 'layout' => 'Maquette', - 'layouts_not_found' => 'Aucune maquette trouvée', - 'saved' => 'La page a été sauvegardée avec succès.', - 'tab' => 'Pages', - 'manage_pages' => 'Gérer les pages statiques', - 'manage_menus' => 'Gérer les menus statiques', - 'access_snippets' => 'Accès aux fragments', - 'manage_content' => 'Gérer le contenu statique', - ], - 'menu' => [ - 'menu_label' => 'Menus', - 'delete_confirmation' => 'Confirmez-vous la suppression des menus sélectionnés ?', - 'no_records' => 'Aucun menu trouvé', - 'new' => 'Nouveau menu', - 'new_name' => 'Nouveau menu', - 'new_code' => 'nouveau-menu', - 'delete_confirm_single' => 'Confirmez-vous la suppression de ce menu ?', - 'saved' => 'Le menu a été sauvegardé avec succès.', - 'name' => 'Nom', - 'code' => 'Code', - 'items' => 'Éléments du menu', - 'add_subitem' => 'Ajouter un élément', - 'code_required' => 'Le Code est requis', - 'invalid_code' => 'Le format du Code est invalide. Le Code peut contenir des chiffres, des lettres et les symboles suivants : _-', - ], - 'menuitem' => [ - 'title' => 'Titre', - 'editor_title' => 'Modifier l’élément du menu', - 'type' => 'Type', - 'allow_nested_items' => 'Autoriser les sous-éléments', - 'allow_nested_items_comment' => 'Les sous-éléments peuvent être générés dynamiquement par les pages statiques et certains des autres types d’élément', - 'url' => 'URL', - 'reference' => 'Référence', - 'search_placeholder' => 'Rechercher toutes les références...', - 'title_required' => 'Le Titre est requis', - 'unknown_type' => 'Type d’élément du menu inconnu', - 'unnamed' => 'Élément de menu sans nom', - 'add_item' => 'Ajouter un élément', - 'new_item' => 'Nouvel élément du menu', - 'replace' => 'Remplacer cet élément part ses sous-éléments générés', - 'replace_comment' => 'Utiliser cette case à cocher pour envoyer les sous-éléments générés au même niveau que cet élément. Cet élément sera lui-même masqué.', - 'cms_page' => 'Page CMS', - 'cms_page_comment' => 'Sélectionnez une page à ouvrir lors d’un clic sur cet élément du menu.', - 'reference_required' => 'La référence de l’élément du menu est requis.', - 'url_required' => 'L’URL est requise', - 'cms_page_required' => 'Sélectionnez une page CMS s’il vous plaît', - 'display_tab' => 'Affichage', - 'hidden' => 'Caché', - 'hidden_comment' => 'Empêcher ce menu d\'apparaître sur le site web.', - 'attributes_tab' => 'Attributs', - 'code' => 'Code', - 'code_comment' => 'Entrez le code de l’élément du menu si vous souhaitez y accéder via l’API.', - 'css_class' => 'Classe CSS', - 'css_class_comment' => 'Entrez un nom de classe CSS pour donner à cet élément de menu une apparence personnalisée.', - 'external_link' => 'Lien externe', - 'external_link_comment' => 'Ouvrir les liens pour ce menu dans une nouvelle fenêtre.', - 'static_page' => 'Page Statique', - 'all_static_pages' => 'Toutes les pages', - ], - 'content' => [ - 'menu_label' => 'Contenu', - 'cant_save_to_dir' => 'L’enregistrement des fichiers de contenu dans le répertoire des pages statiques n’est pas autorisé.', - ], - 'sidebar' => [ - 'add' => 'Ajouter', - ], - 'object' => [ - 'invalid_type' => 'Type d’objet inconnu', - 'not_found' => 'L’objet demandé n’a pas été trouvé.', - ], - 'editor' => [ - 'title' => 'Titre', - 'new_title' => 'Nouveau titre de la page', - 'content' => 'Contenu', - 'url' => 'URL', - 'filename' => 'Nom du fichier', - 'layout' => 'Maquette', - 'description' => 'Description', - 'preview' => 'Aperçu', - 'enter_fullscreen' => 'Activer le mode plein écran', - 'exit_fullscreen' => 'Annuler le mode plein écran', - 'hidden' => 'Caché', - 'hidden_comment' => 'Les pages cachées sont seulement accessibles aux administrateurs connectés.', - 'navigation_hidden' => 'Masquer dans la navigation', - 'navigation_hidden_comment' => 'Cochez cette case pour masquer cette page dans les menus et le fil d’ariane générés automatiquement.', - ], - 'snippet' => [ - 'menu_label' => 'Fragments', - ], - 'component' => [ - 'static_page_name' => 'Page Statique', - 'static_page_description' => 'Affiche une page statique dans une maquette du CMS.', - 'static_page_use_content_name' => 'Affiche la section de contenu', - 'static_page_use_content_description' => 'Si cette case n\'est pas cochée, la section de contenu n\'apparaîtra pas lors de la modification de la page statique. Le contenu de la page sera déterminé uniquement à l\'aide d\'espaces réservés et de variables.', - 'static_page_default_name' => 'Disposition par défault', - 'static_page_default_description' => 'Définit cette mise en page par défault pour les nouvelles pages', - 'static_page_child_layout_name' => 'Mise en page de la sous-page', - 'static_page_child_layout_description' => 'La mise en page à utiliser par défault pour les nouvelles sous-pages', - 'static_menu_name' => 'Menu Statique', - 'static_menu_description' => 'Affiche un menu dans une maquette du CMS.', - 'static_menu_code_name' => 'Menu', - 'static_menu_code_description' => 'Spécifiez le code du menu que le composant devrait afficher.', - 'static_breadcrumbs_name' => 'Breadcrumbs statique', - 'static_breadcrumbs_description' => 'Affiche l\' aide à la navigation dans une page statique.', - 'child_pages_name' => 'Pages enfants', - 'child_pages_description' => 'Affiche une liste de pages enfants pour la page en cours', - ], -]; diff --git a/lang/hu/lang.php b/lang/hu/lang.php deleted file mode 100644 index 6803ec79..00000000 --- a/lang/hu/lang.php +++ /dev/null @@ -1,125 +0,0 @@ - [ - 'name' => 'Oldalak', - 'description' => 'Oldalak, menük, tartalmak és kódrészletek menedzselése.', - ], - 'page' => [ - 'menu_label' => 'Oldalak', - 'template_title' => '%s Oldalak', - 'delete_confirmation' => 'Valóban törölni akarja a kijelölt oldalakat és azok aloldalait?', - 'no_records' => 'Nincs létrehozva oldal', - 'delete_confirm_single' => 'Valóban törölni akarja ezt az oldalt és aloldalait?', - 'new' => 'Új oldal', - 'add_subpage' => 'Aloldal hozzáadása', - 'invalid_url' => 'Érvénytelen a webcím formátuma. Perjellel kell kezdődnie, és számokat, latin betűket, valamint a következő szimbólumokat tartalmazhatja: _-/.', - 'url_not_unique' => 'Egy másik oldal már használja ezt a webcímet.', - 'layout' => 'Elrendezés', - 'layouts_not_found' => 'Nincs létrehozva elrendezés.', - 'saved' => 'Az oldal mentése sikerült.', - 'tab' => 'Oldalak', - 'manage_pages' => 'Oldalak kezelése', - 'manage_menus' => 'Menük kezelése', - 'access_snippets' => 'Kódrészletek kezelése', - 'manage_content' => 'Tartalom kezelése', - ], - 'menu' => [ - 'menu_label' => 'Menük', - 'delete_confirmation' => 'Valóban törölni akarja a kijelölt menüket?', - 'no_records' => 'Nincs létrehozva menü', - 'new' => 'Új menü', - 'new_name' => 'Új menü', - 'new_code' => 'uj-menu', - 'delete_confirm_single' => 'Valóban törölni akarja ezt a menüt?', - 'saved' => 'A menü mentése sikerült.', - 'name' => 'Név', - 'code' => 'Kód', - 'items' => 'Menüpont', - 'add_subitem' => 'Almenü hozzáadása', - 'code_required' => 'A Kód kötelező', - 'invalid_code' => 'Érvénytelen a kód formátuma. Csak számokat, latin betűket és a következő szimbólumokat tartalmazhatja: _-', - ], - 'menuitem' => [ - 'title' => 'Cím', - 'editor_title' => 'Menüpont szerkesztése', - 'type' => 'Típus', - 'allow_nested_items' => 'Beágyazott menüpontok engedélyezése', - 'allow_nested_items_comment' => 'A beágyazott menüpontokat az oldal és néhány más menüpont típus dinamikusan generálhatja', - 'url' => 'Webcím', - 'reference' => 'Hivatkozás', - 'search_placeholder' => 'Keresés...', - 'title_required' => 'A cím megadása kötelező', - 'unknown_type' => 'Ismeretlen menüponttípus', - 'unnamed' => 'Névtelen menüpont', - 'add_item' => 'Menüpont hozzáadása', - 'new_item' => 'Új menüpont', - 'replace' => 'A menüpont kicserélése a generált gyermekeire', - 'replace_comment' => 'Ennek a jelölőnégyzetnek a használatával viheti a generált menüpontokat az ezen menüpont által azonos szintre. Maga ez a menüpont rejtett marad.', - 'cms_page' => 'Oldal', - 'cms_page_comment' => 'Válassza ki a menüre kattintáskor megnyitni kívánt oldalt.', - 'reference_required' => 'A menüpont hivatkozás kitöltése kötelező.', - 'url_required' => 'A webcím megadása kötelező', - 'cms_page_required' => 'Válasszon egy oldalt', - 'display_tab' => 'Megjelenés', - 'hidden' => 'Rejtett', - 'hidden_comment' => 'Nem jelenik meg a felhasználói felületen.', - 'attributes_tab' => 'Tulajdonságok', - 'code' => 'Kód', - 'code_comment' => 'Az API eléréshez szükséges egyedi azonosító.', - 'css_class' => 'CSS osztály', - 'css_class_comment' => 'Egyedi megjelenés esetén szükséges megadni.', - 'external_link' => 'Külső hivatkozás', - 'external_link_comment' => 'A link új ablakban fog megjelenni.', - 'static_page' => 'Oldalak', - 'all_static_pages' => 'Összes oldal', - ], - 'content' => [ - 'menu_label' => 'Tartalom', - 'saved' => 'A tartalom mentése sikerült.', - 'cant_save_to_dir' => 'A fájlok mentése a "static-pages" könyvtárba nem engedélyezett.', - ], - 'sidebar' => [ - 'add' => 'Hozzáadás', - ], - 'object' => [ - 'invalid_type' => 'Ismeretlen objektumtípus', - 'unauthorized_type' => 'Nem jogosult a következő objektum(ok) kezelésére: :type', - 'not_found' => 'A kért objektum nem található.', - ], - 'editor' => [ - 'title' => 'Cím', - 'new_title' => 'Új oldal címe', - 'content' => 'Tartalom', - 'url' => 'Webcím', - 'filename' => 'Fájlnév', - 'layout' => 'Elrendezés', - 'description' => 'Leírás', - 'preview' => 'Előnézet', - 'enter_fullscreen' => 'Váltás teljes képernyős módra', - 'exit_fullscreen' => 'Kilépés a teljes képernyős módból', - 'hidden' => 'Rejtett', - 'hidden_comment' => 'A rejtett oldalakhoz csak a bejelentkezett kiszolgáló oldali felhasználók férhetnek hozzá.', - 'navigation_hidden' => 'Elrejtés a navigációban', - 'navigation_hidden_comment' => 'Jelölje be ezt a jelölőnégyzetet ennek a oldalnak az automatikusan generált menükből és útkövetésekből való elrejtéséhez.', - ], - 'snippet' => [ - 'menu_label' => 'Kódrészletek', - ], - 'component' => [ - 'static_page_name' => 'Statikus oldal', - 'static_page_description' => 'Oldalak megjelenítése.', - 'static_page_use_content_name' => 'Tartalom mező használata', - 'static_page_use_content_description' => 'Ha nem engedélyezi ezt, akkor a tartalmi rész nem fog megjelenni az oldal szerkesztésénél. Az oldal tartalmát kizárólag a változók fogják meghatározni.', - 'static_page_default_name' => 'Alapértelmezett elrendezés', - 'static_page_default_description' => 'Minden új oldal ezt az elrendezést fogja hasznáni alapértelmezettként.', - 'static_page_child_layout_name' => 'Aloldal elrendezés', - 'static_page_child_layout_description' => 'Minden új aloldal ezt az elrendezést fogja használni alapértelmezettként.', - 'static_menu_name' => 'Statikus menü', - 'static_menu_description' => 'Menük megjelenítése.', - 'static_menu_code_name' => 'Menü', - 'static_menu_code_description' => 'Speciális kód a megjelenő menünek.', - 'static_breadcrumbs_name' => 'Statikus kenyérmorzsa', - 'static_breadcrumbs_description' => 'Kenyérmorzsa megjelenítése.', - 'child_pages_name' => 'Aloldalak', - 'child_pages_description' => 'Megjeleníti az aktuális oldal aloldalainak listáját.', - ], -]; diff --git a/lang/it/lang.php b/lang/it/lang.php deleted file mode 100644 index 2ba69071..00000000 --- a/lang/it/lang.php +++ /dev/null @@ -1,96 +0,0 @@ - [ - 'name' => 'Pages', - 'description' => 'Funzionalità di pagine & menu.', - ], - 'page' => [ - 'menu_label' => 'Pagine', - 'template_title' => '%s Pagine', - 'delete_confirmation' => 'Vuoi davvero eliminare le pagine selezionate? L\'operazione cancellerà anche le sottopagine, se presenti.', - 'no_records' => 'Nessuna pagina trovata', - 'delete_confirm_single' => 'Vuoi davvero eliminare questa pagina? L\'operazione cancellerà anche le sottopagine, se presenti.', - 'new' => 'Nuova pagina', - 'add_subpage' => 'Aggiungi sottopagina', - 'invalid_url' => 'Formato dell\'URL non valido. L\'URL deve iniziare con una barra e può contenere numeri, lettere latine e i seguenti simboli: _-/.', - 'url_not_unique' => 'L\'URL è già utilizzato da un\'altra pagina.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Layouts non trovato', - 'saved' => 'Pagina salvata con successo.', - 'tab' => 'Pagine', - 'manage_pages' => 'Gestisci pagine', - 'manage_menus' => 'Gestisci menu', - 'access_snippets' => 'Accedi agli snippet', - 'manage_content' => 'Gestisci contenuti', - ], - 'menu' => [ - 'menu_label' => 'Menu', - 'delete_confirmation' => 'Vuoi davvero eliminare i menu selezionati?', - 'no_records' => 'Nessun menu trovato', - 'new' => 'Nuovo menu', - 'new_name' => 'Nuovo menu', - 'new_code' => 'nuovo-menu', - 'delete_confirm_single' => 'Vuoi davvero eliminare questo menu?', - 'saved' => 'Menu salvato con successo.', - 'name' => 'Nome', - 'code' => 'Codice', - 'items' => 'Voci di menu', - 'add_subitem' => 'Aggiungi sottomenu', - 'code_required' => 'Il Codice è obbligatorio', - 'invalid_code' => 'Formato del Codice non valido. Il Codice può contenere numeri, lettere latine e i seguenti simboli: _-', - ], - 'menuitem' => [ - 'title' => 'Titolo', - 'editor_title' => 'Modifica voce di menu', - 'type' => 'Tipo', - 'allow_nested_items' => 'Consenti elementi nidificati', - 'allow_nested_items_comment' => 'Gli elementi nidificati possono essere generati dinamicamente dalle pagine e altre tipologie di elementi', - 'url' => 'URL', - 'reference' => 'Riferimento', - 'title_required' => 'Il Titolo è obbligatorio', - 'unknown_type' => 'Tipologia di menu sconosciuta', - 'unnamed' => 'Voce di menu senza nome', - 'add_item' => 'Aggiungi elemento', - 'new_item' => 'Nuova voce di menu', - 'replace' => 'Sostituisci questo elemento con i figli generati', - 'replace_comment' => 'Usa questa checkbox per inserire le voci di menu generate allo stesso livello di questo elemento. Questa voce verrà nascosta.', - 'cms_page' => 'Pagine CMS', - 'cms_page_comment' => 'Seleziona una pagina del CMS da aprire quando viene selezionata la voce di menu.', - 'reference_required' => 'Il riferimento della voce di menu è obbligatorio.', - 'url_required' => 'L\'URL è obbligatorio', - 'cms_page_required' => 'Seleziona una pagina CMS', - 'code' => 'Codice', - 'code_comment' => 'Inserisci il codice della voce di menu se vuoi accedervi con l\'API.', - 'static_page' => 'Pagine', - 'all_static_pages' => 'Tutte le pagine', - ], - 'content' => [ - 'menu_label' => 'Contenuti', - 'cant_save_to_dir' => 'Salvataggio dei file di contenuto nella directory static-pages non consentito.', - ], - 'sidebar' => [ - 'add' => 'Aggiungi', - ], - 'object' => [ - 'invalid_type' => 'Tipo di oggetto sconosciuto', - 'not_found' => 'Oggetto richiesto non trovato.', - ], - 'editor' => [ - 'title' => 'Titolo', - 'new_title' => 'Titolo nuova pagina', - 'content' => 'Contenuto', - 'url' => 'URL', - 'filename' => 'Nome file', - 'layout' => 'Layout', - 'description' => 'Descrizione', - 'preview' => 'Anteprima', - 'enter_fullscreen' => 'Abilita visualizzazione a schermo intero', - 'exit_fullscreen' => 'Esci dalla visualizzazione a schermo intero', - 'hidden' => 'Nascosto', - 'hidden_comment' => 'Le pagine nascoste sono accessibili soltanto dagli utenti che hanno effettuato l\'accesso al pannello di controllo.', - 'navigation_hidden' => 'Nascondi dalla navigazione', - 'navigation_hidden_comment' => 'Seleziona questa checkbox per nascondere questa pagina dai menu e dalle barre di navigazione generate automaticamente.', - ], - 'snippet' => [ - 'menu_label' => 'Snippet', - ], -]; diff --git a/lang/lv/lang.php b/lang/lv/lang.php deleted file mode 100644 index 2a6dfeb9..00000000 --- a/lang/lv/lang.php +++ /dev/null @@ -1,101 +0,0 @@ - [ - 'name' => 'Lapas', - 'description' => 'Lapu un izvēļņu funkcijas.', - ], - 'page' => [ - 'menu_label' => 'Lapas', - 'template_title' => '%s Lapas', - 'delete_confirmation' => 'Vai tu tiešām vēlies dzēst izvēlētās lapas? Šī operācija izdzēsīs arī apakšlapas (ja tādas ir).', - 'no_records' => 'Neviena lapa netika atrasta', - 'delete_confirm_single' => 'Vai tu tiešām vēlies dzēst izvēlēto lapu? Šī operācija izdzēsīs arī apakšlapas (ja tādas ir).', - 'new' => 'Jauna lapa', - 'add_subpage' => 'Pievienot apakšlapu', - 'invalid_url' => 'Nekorekts saites formāts. Saitei vajadzētu sākties ar slīpsvītru un tā var saturēt ciparus, latīnu alfabēta burtus, slīpsvītras un sekojošos sibolus: _-/.', - 'url_not_unique' => 'Šādu saiti izmanto jau kāda cita lapa.', - 'layout' => 'Izkārtojums', - 'layouts_not_found' => 'Izkārtojumi netika atrasti', - 'saved' => 'Lapa tika veiksmīgi saglabāta.', - 'tab' => 'Lapas', - 'manage_pages' => 'Pieeja labot statiskās lapas', - 'manage_menus' => 'Pieeja labot statiskās izvēlnes', - 'access_snippets' => 'Pieeja koda fragmentiem', - 'manage_content' => 'Pieeja labot statisko saturu', - ], - 'menu' => [ - 'menu_label' => 'Izvēlnes', - 'delete_confirmation' => 'Vai tu tiešām vēlies dzēst izvēlētās izvēlnes?', - 'no_records' => 'Izvēlnes netika atrastas', - 'new' => 'Jauna izvēlne', - 'new_name' => 'Jauna izvēlne', - 'new_code' => 'jauna-izvelne', - 'delete_confirm_single' => 'Vai tu tiešām vēlies dzēst šo izvēlni?', - 'saved' => 'Izvēlne tika veiksmīgi saglabāta', - 'name' => 'Vārds', - 'code' => 'Kods', - 'items' => 'Izvēlnes priekšmeti', - 'add_subitem' => 'Pievienot apakšpriekšmetu', - 'code_required' => 'Kods ir obligāts lauks.', - 'invalid_code' => 'Nepreaizs koda formāts. Kods var saturēt ciparus, latīnu alfabēta burtus un sekojošos simbolus: _-', - ], - 'menuitem' => [ - 'title' => 'Nosaukums', - 'editor_title' => 'Labot izvēlnes priekšmetu', - 'type' => 'Tips', - 'allow_nested_items' => 'Atļaut iegultos priekšmetus', - 'allow_nested_items_comment' => 'Iegultie priekšmeti var tikt dinamiski ģenerēti', - 'url' => 'Saite', - 'reference' => 'Atsauce', - 'title_required' => 'Nosaukuma lauks ir obligāts', - 'unknown_type' => 'Nezināms izvēlnes tips', - 'unnamed' => 'Izvēlnes priekšmets bez nosaukums', - 'add_item' => 'Pievienot Priekšmetu', - 'new_item' => 'Jauns izvēlnes priekšmets', - 'replace' => 'Aizvietot šo priekšmetu ar tā ģenerētajiem bērniem', - 'cms_page' => 'CMS lapa', - 'cms_page_comment' => 'Izvēlies lapu, kas atvērsies, kad tiks noklikšķiāts uz šī izvēlnes priekšmeta.', - 'reference_required' => 'Izvēlnes priekšmeta atsauce ir obligāts lauks.', - 'url_required' => 'Saite ir obligāti jāievada', - 'cms_page_required' => 'Lūdzu, izvēlies CMS lapu', - 'code' => 'Kods', - 'code_comment' => 'Ievadi izvēlnes priekšmeta kodu, ja tam vēlies piekļūt izmantojot API.', - 'static_page' => 'Statiska lapa', - 'all_static_pages' => 'Visas statiskās lapas', - ], - 'content' => [ - 'menu_label' => 'Saturs', - 'cant_save_to_dir' => 'Satura failu saglabāšana statisko lapu direktorijā nav atļauta.S', - ], - 'sidebar' => [ - 'add' => 'Pievienot', - ], - 'object' => [ - 'invalid_type' => 'Nezināms objekta tips', - 'not_found' => 'Pieprasītais objekts netika atrasts.', - ], - 'editor' => [ - 'title' => 'Nosaukums', - 'new_title' => 'Jaunās lapas nosaukums', - 'content' => 'Saturs', - 'url' => 'Saite', - 'filename' => 'Faila nosaukums', - 'layout' => 'Izkārtojums', - 'description' => 'Skaidrojums', - 'preview' => 'Priekšskats', - 'enter_fullscreen' => 'Atvērt pilnekrāna režīmu', - 'exit_fullscreen' => 'Aizvērt pilnekrāna režīmu', - 'hidden' => 'Paslēpts', - 'hidden_comment' => 'Paslēptās lapas varēs redzēt tikai ielogojušies back-end lietotāji.', - 'navigation_hidden' => 'Paslēpt navigācijā', - 'navigation_hidden_comment' => 'Atķeksē šo kasti, lai automātiski ģenerētu izvēlnes un breadcrumbus.', - ], - 'snippet' => [ - 'menu_label' => 'Koda fragmenti', - ], - 'component' => [ - 'static_page_description' => 'Izvada statisku lapu CMS iegultnē.', - 'static_menu_description' => 'Izvada izvēlni CMS iegultnē.', - 'static_menu_menu_code' => 'Specificē komponentes kodu, ko izvadīt', - 'static_breadcrumbs_description' => 'Izvada breadcrumbus CMS iegultnē.', - ], -]; diff --git a/lang/nb-no/lang.php b/lang/nb-no/lang.php deleted file mode 100644 index 43a246f1..00000000 --- a/lang/nb-no/lang.php +++ /dev/null @@ -1,93 +0,0 @@ - [ - 'name' => 'Sider', - 'description' => 'Side- og menyfunksjoner.', - ], - 'page' => [ - 'menu_label' => 'Sider', - 'template_title' => '%s Sider', - 'delete_confirmation' => 'Vil du virkelig slette de valgte sidene? Hvis siden har undersider, blir de også slettet.', - 'no_records' => 'Ingen sider funnet', - 'delete_confirm_single' => 'Vil du virkelig slette denne siden? Hvis siden har undersider, blir de også slettet.', - 'new' => 'Ny side', - 'add_subpage' => 'Ny underside', - 'invalid_url' => 'Ugyldig URL-format. URL-en skal starte med en skråstrek og kan bare inneholde tall, latinske bokstaver og følgende symboler: _-/.', - 'url_not_unique' => 'URL-en er allerede i bruk av en annen side.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Ingen layouts funnet', - 'saved' => 'Siden har blitt lagret.', - 'manage_pages' => 'Administrer statiske sider', - 'manage_menus' => 'Administrer statiske menyer', - 'access_snippets' => 'Tilgang til snippets', - 'manage_content' => 'Administrer statisk innhold', - ], - 'menu' => [ - 'menu_label' => 'Menyer', - 'delete_confirmation' => 'Vil du virkelig slette valgte menyer?', - 'no_records' => 'Ingen elementer funnet', - 'new' => 'Ny meny', - 'new_name' => 'Ny meny', - 'new_code' => 'new-menu', - 'delete_confirm_single' => 'Vil du virkelig slette denne menyen?', - 'saved' => 'Menyen har blitt lagret.', - 'name' => 'Navn', - 'code' => 'Kode', - 'items' => 'Elementer', - 'add_subitem' => 'Nytt underelement', - 'code_required' => 'En kode kreves.', - 'invalid_code' => 'Ugyldig kode-format. Koden kan inneholde tall, latinske bokstaver og følgende symboler: _-', - ], - 'menuitem' => [ - 'title' => 'Tittel', - 'editor_title' => 'Endre element', - 'type' => 'Type', - 'allow_nested_items' => 'Tillat underelementer', - 'allow_nested_items_comment' => 'Underelementer kan bli generert dynamisk av statiske sider og andre elementtyper', - 'url' => 'URL', - 'reference' => 'Referanse', - 'title_required' => 'Tittel kreves.', - 'unknown_type' => 'Ukjent elementtype', - 'unnamed' => 'Navnløs elementtype', - 'add_item' => 'Legg til element', - 'new_item' => 'Nytt element', - 'replace' => 'Erstatt dette elementet med sine underelementer', - 'replace_comment' => 'Huk av denne boksen for å skjule dette elementet. Underelementer blir fremdeles synlige.', - 'cms_page' => 'CMS-side', - 'cms_page_comment' => 'Velg hvilken side som skal åpnes når man trykker på linken.', - 'reference_required' => 'En referanse kreves.', - 'url_required' => 'En URL kreves.', - 'cms_page_required' => 'Vennligst velg en CMS-side', - 'code' => 'Kode', - 'code_comment' => 'Velg en elementkode hvis du trenger tilgang via API-en. (valgfritt)', - ], - 'content' => [ - 'menu_label' => 'Innhold', - 'cant_save_to_dir' => 'Å lagre innhold til files i static-pages-mappen er ikke tillatt.', - ], - 'sidebar' => [ - 'add' => 'Legg til', - ], - 'object' => [ - 'invalid_type' => 'Ukjent objekttype', - 'not_found' => 'Det forespurte objektet ble ikke funnet.', - ], - 'editor' => [ - 'title' => 'Tittel', - 'new_title' => 'Tittel på siden', - 'content' => 'Innhold', - 'url' => 'URL', - 'filename' => 'Filnavn', - 'layout' => 'Layout', - 'description' => 'Beskrivelse', - 'preview' => 'Forhåndsvis', - 'enter_fullscreen' => 'Fullskjermmodus', - 'exit_fullscreen' => 'Avslutt fullskjermmodus', - 'hidden' => 'Skjult', - 'hidden_comment' => 'Kun backend-brukere har tilgang til skjulte sider.', - 'navigation_hidden' => 'Gjem i menyer', - 'navigation_hidden_comment' => 'Huk av denne boksen for å skjule denne siden i genererte menyer', - ], - 'snippet' => [ - 'menu_label' => 'Snippets', - ], -]; diff --git a/lang/nl/lang.php b/lang/nl/lang.php deleted file mode 100644 index c74d8fc5..00000000 --- a/lang/nl/lang.php +++ /dev/null @@ -1,96 +0,0 @@ - [ - 'name' => 'Pagina\'s', - 'description' => 'Pagina & menu functionaliteit.', - ], - 'page' => [ - 'menu_label' => 'Pagina\'s', - 'template_title' => '%s Pagina\'s', - 'delete_confirmation' => 'Weet u zeker dat u de geselecteerde pagina\'s wilt verwijderen? Ook eventuele subpagina\'s zullen hierdoor verwijderd worden.', - 'no_records' => 'Geen pagina\'s gevonden', - 'delete_confirm_single' => 'Weet u zeker dat u deze pagina wilt verwijderen? Ook eventuele subpagina\'s zullen hierdoor verwijderd worden.', - 'new' => 'Nieuwe pagina', - 'add_subpage' => 'Subpagina toevoegen', - 'invalid_url' => 'Ongeldige URL-structuur. De URL moet beginnen met een slash en kan enkel cijfers, Latijnse letters en deze symbolen bevatten: _-/.', - 'url_not_unique' => 'Deze URL wordt al gebruikt door een andere pagina.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Geen layouts gevonden', - 'saved' => 'De pagina is succesvol opgeslagen.', - 'tab' => 'Pagina\'s', - 'manage_pages' => 'Beheer statische pagina\'s', - 'manage_menus' => 'Beheer statische menu\'s', - 'access_snippets' => 'Toegang tot blokken', - 'manage_content' => 'Beheer statische inhoud', - ], - 'menu' => [ - 'menu_label' => 'Menu\'s', - 'delete_confirmation' => 'Weet u zeker dat u de geselecteerde menu\'s wilt verwijderen?', - 'no_records' => 'Geen menu\'s gevonden', - 'new' => 'Nieuw menu', - 'new_name' => 'Nieuw menu', - 'new_code' => 'nieuw-menu', - 'delete_confirm_single' => 'Weet u zeker dat u dit menu wilt verwijderen?', - 'saved' => 'Het menu is opgeslagen.', - 'name' => 'Naam', - 'code' => 'Code', - 'items' => 'Menu items', - 'add_subitem' => 'Subitem toevoegen', - 'code_required' => 'Code is verplicht', - 'invalid_code' => 'Ongeldige code-structuur. De Code kan enkel cijfers, Latijnse letters en deze symbolen bevatten: _-', - ], - 'menuitem' => [ - 'title' => 'Titel', - 'editor_title' => 'Bewerk Menu Item', - 'type' => 'Type', - 'allow_nested_items' => 'Accepteer geneste items', - 'allow_nested_items_comment' => 'Geneste items worden dynamisch gegenereerd door statische pagina\'s en sommige andere types.', - 'url' => 'URL', - 'reference' => 'Referentie', - 'title_required' => 'Titel is verplicht', - 'unknown_type' => 'Onbekend menu item type', - 'unnamed' => 'Onbenoemd menu item', - 'add_item' => 'Item toevoegen', - 'new_item' => 'Nieuw menu item', - 'replace' => 'Vervang dit item door de gegenereerde subitems', - 'replace_comment' => 'Wanneer u deze optie aanvinkt, zullen de gegenereerd menu items worden getoond op het niveau van dit item. Dit item zelf zal verborgen blijven.', - 'cms_page' => 'CMS Pagina', - 'cms_page_comment' => 'Selecteer een pagina om te openen wanneer op het menu item geklikt wordt.', - 'reference_required' => 'Een referentie is verplicht.', - 'url_required' => 'Een URL is verplicht', - 'cms_page_required' => 'Gelieve een CMS Pagina te selecteren', - 'code' => 'Code', - 'code_comment' => 'Geef de menu item code op indien u deze wilt benaderen via de API.', - 'static_page' => 'Statische pagina', - 'all_static_pages' => 'Alle statische pagina\'s', - ], - 'content' => [ - 'menu_label' => 'Inhoud', - 'cant_save_to_dir' => 'Het is niet toegelaten bestanden met inhoud op te slaan in de static-pages map.', - ], - 'sidebar' => [ - 'add' => 'Toevoegen', - ], - 'object' => [ - 'invalid_type' => 'Onbekend object type', - 'not_found' => 'Het gevraagde object is niet gevonden.', - ], - 'editor' => [ - 'title' => 'Titel', - 'new_title' => 'Nieuwe pagina titel', - 'content' => 'Inhoud', - 'url' => 'URL', - 'filename' => 'Bestandsnaam', - 'layout' => 'Layout', - 'description' => 'Beschrijving', - 'preview' => 'Voorbeeld', - 'enter_fullscreen' => 'Volledig scherm openen', - 'exit_fullscreen' => 'Volledig scherm afsluiten', - 'hidden' => 'Verborgen', - 'hidden_comment' => 'Verborgen pagina\'s zijn alleen toegankelijk voor ingelogde gebruikers.', - 'navigation_hidden' => 'Verbergen in de navigatie', - 'navigation_hidden_comment' => 'Indien aangevinkt, zal deze pagina niet weergegeven worden in automatisch gegenereerde menu\'s en kruimelpaden (breadcrumbs).', - ], - 'snippet' => [ - 'menu_label' => 'Blokken', - ], -]; diff --git a/lang/pl/lang.php b/lang/pl/lang.php deleted file mode 100644 index d0e54685..00000000 --- a/lang/pl/lang.php +++ /dev/null @@ -1,124 +0,0 @@ - [ - 'name' => 'Strony', - 'description' => 'Strony statyczne oraz menu.', - ], - 'page' => [ - 'menu_label' => 'Strony', - 'template_title' => '%s Strony', - 'delete_confirmation' => 'Czy na pewno chcesz usunąć wybrane strony? Podstrony również zostaną usnięte.', - 'no_records' => 'Nie znaleziono stron', - 'delete_confirm_single' => 'Czy na pewno chcesz usunąć stronę? Podstrony również zostaną usnięte.', - 'new' => 'Nowa strona', - 'add_subpage' => 'Dodaj podstronę', - 'invalid_url' => 'Niewprawidłowy format URL lub niedozwolone znaki.', - 'url_not_unique' => 'Podany URL istnieje już w bazie.', - 'layout' => 'Układ', - 'layouts_not_found' => 'Brak układów', - 'saved' => 'Strona została zapisana poprawnie.', - 'tab' => 'Strony', - 'manage_pages' => 'Zarządzaj stronami statycznymi', - 'manage_menus' => 'Zarządzaj menu statycznymi', - 'access_snippets' => 'Fragmenty', - 'manage_content' => 'Zarządzaj treścią statyczną', - ], - 'menu' => [ - 'menu_label' => 'Menu', - 'delete_confirmation' => 'Czy na pewno chcesz usunąć wybrane menu?', - 'no_records' => 'Nie znaleziono menu', - 'new' => 'Nowe menu', - 'new_name' => 'Nowe menu', - 'new_code' => 'nowe-menu', - 'delete_confirm_single' => 'Czy na pewno chcesz usunąć wybrane menu?', - 'saved' => 'Menu zostało zapisane poprawnie.', - 'name' => 'Nazwa', - 'code' => 'Kod systemowy', - 'items' => 'Elementy menu', - 'add_subitem' => 'Dodaj element', - 'code_required' => 'Kod systemowy jest wymagany', - 'invalid_code' => 'Nieprawidłowy kod systemowy. Nie może zawierać znaków specjalnych', - ], - 'menuitem' => [ - 'title' => 'Tytuł', - 'editor_title' => 'Edytuj element', - 'type' => 'Typ', - 'allow_nested_items' => 'Pozwól na zagnieżdżanie elementów', - 'allow_nested_items_comment' => 'Zagnieżdżone elementy mogą być utworzone dynamicznie np ze stron statycznych', - 'url' => 'URL', - 'reference' => 'Referencja', - 'search_placeholder' => 'Przeszukaj wszystkie referencje...', - 'title_required' => 'Tytuł jest wymagany', - 'unknown_type' => 'Nieznany typ elementu', - 'unnamed' => 'Brak nazwy typu elementu', - 'add_item' => 'Dodaj Element', - 'new_item' => 'Nowy element menu', - 'replace' => 'Zamień element na elementy wenątrz tego elementu', - 'replace_comment' => 'Użyj tej opcji aby elementy znajdujące sie w tym elemencie były wygenerowane na poziomie tego elementu a sam element zostanie ukryty.', - 'cms_page' => 'Strona CMS', - 'cms_page_comment' => 'Wybierz stronę z cms do której ma kierować', - 'reference_required' => 'Referencja jest wymagana', - 'url_required' => 'URL jest wymagany', - 'cms_page_required' => 'Wybierz stronę z CMS', - 'display_tab' => 'Wyświetlanie', - 'hidden' => 'Ukryj', - 'hidden_comment' => 'Ukryj ten element menu na stronie', - 'attributes_tab' => 'Atrybuty', - 'code' => 'Kod systemowy', - 'code_comment' => 'Wprowadź kod systemowy aby móc go używać w API.', - 'css_class' => 'Klasa CSS', - 'css_class_comment' => 'Wprowadź klasę CSS, aby nadać temu elementowi niestandardowy wygląd', - 'external_link' => 'Zewnętrzny link', - 'external_link_comment' => 'Adres linku zostanie otworzony w nowym oknie', - 'static_page' => 'Strona statyczna', - 'all_static_pages' => 'Wszystkie strony statyczne', - ], - 'content' => [ - 'menu_label' => 'Treść', - 'cant_save_to_dir' => 'Zapis treści jest niemożliwy. Sprawdź dostęp do folderu treści statycznych.', - ], - 'sidebar' => [ - 'add' => 'Dodaj', - ], - 'object' => [ - 'invalid_type' => 'Nieznany typ obiektu', - 'not_found' => 'Nie znaleziono objektu.', - ], - 'editor' => [ - 'title' => 'Tytuł', - 'new_title' => 'Nowy tytuł strony', - 'content' => 'Treść', - 'url' => 'URL', - 'filename' => 'Nazwa pliku', - 'layout' => 'Układ', - 'description' => 'Opis', - 'preview' => 'Podgląd', - 'enter_fullscreen' => 'Pełny ekran', - 'exit_fullscreen' => 'Zamknij pełny ekran', - 'hidden' => 'Ukryta', - 'hidden_comment' => 'Ukryte strony są dostępne tylko dla zalogowanych administratorów.', - 'navigation_hidden' => 'Ukryj stronę w nawigacji', - 'navigation_hidden_comment' => 'Zaznacz aby usunąć strone z automatycznego generowania menu oraz ścieżek (breadcrumbs).', - ], - 'snippet' => [ - 'menu_label' => 'Fragmenty', - ], - 'component' => [ - 'static_page_name' => 'Strona statyczna', - 'static_page_description' => 'Dodaje zawartość statycznej strony.', - 'static_page_use_content_name' => 'Użyj pola zawartości strony', - 'static_page_use_content_description' => 'Jeżeli odznaczone, sekcja treści nie pojawi się podczas edycji strony statycznej. Zawartość strony będzie ustalana wyłącznie na podstawie symboli zastępczych i zmiennych', - 'static_page_default_name' => 'Domyślny układ', - 'static_page_default_description' => 'Definiuje ten układ jako domyślny dla nowych stron', - 'static_page_child_layout_name' => 'Układ podstrony', - 'static_page_child_layout_description' => 'Układ, który będzie używany jako domyślny dla każdej nowej podstrony', - 'static_menu_name' => 'Menu', - 'static_menu_description' => 'Dodaje menu do strony.', - 'static_menu_code_name' => 'Menu', - 'static_menu_code_description' => 'Podaj kod systemowy menu, które ma zostać wyświetlone.', - 'static_breadcrumbs_name' => 'Okruszki (Breadcrumbs)', - 'static_breadcrumbs_description' => 'Zwraca ścieżkę stron statycznych.', - 'child_pages_name' => 'Strony podrzędne', - 'child_pages_description' => 'Wyświetla listę stron podrzędnych dla aktualnej strony', - 'static_menu_menu_code' => 'Podaj kod systemowy menu', - ], -]; diff --git a/lang/pt-br/lang.php b/lang/pt-br/lang.php deleted file mode 100644 index 56e0bb65..00000000 --- a/lang/pt-br/lang.php +++ /dev/null @@ -1,94 +0,0 @@ - [ - 'name' => 'Páginas', - 'description' => 'Gerenciar páginas e menus.', - ], - 'page' => [ - 'menu_label' => 'Páginas', - 'template_title' => '%s Páginas', - 'delete_confirmation' => 'Tem certeza que deseja excluir as páginas selecionadas? Todas as subpáginas também serão excluídas.', - 'no_records' => 'Nenhuma página encontrada', - 'delete_confirm_single' => 'Tem certeza que deseja excluir a página selecionada? Todas as subpáginas também serão excluídas.', - 'new' => 'Nova página', - 'add_subpage' => 'Adicionar subpágina', - 'invalid_url' => 'Formato inválido de URL. A URL deve iniciar com o símbolo / e pode conter apenas dígitos, letras latinas e os seguintes símbolos: _-/.', - 'url_not_unique' => 'Esta URL já está sendo utilizada por outra página.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Nenhum layout encontrado', - 'saved' => 'Página salva com sucesso.', - 'tab' => 'Páginas', - 'manage_pages' => 'Gerenciar páginas estáticas', - 'manage_menus' => 'Gerenciar menus estáticos', - 'access_snippets' => 'Acessar fragmentos', - 'manage_content' => 'Gerenciar conteúdos estáticos', - ], - 'menu' => [ - 'menu_label' => 'Menus', - 'delete_confirmation' => 'Tem certeza que deseja excluir os menus selecionados?', - 'no_records' => 'Nenhum item encontrado', - 'new' => 'Novo menu', - 'new_name' => 'Novo menu', - 'new_code' => 'novo-menu', - 'delete_confirm_single' => 'Tem certeza que deseja excluir este menu?', - 'saved' => 'Menu salvo com sucesso.', - 'name' => 'Nome', - 'code' => 'Código', - 'items' => 'Itens do menu', - 'add_subitem' => 'Adicionar subitem', - 'code_required' => 'O código é necessário', - 'invalid_code' => 'Formato inválido de código. O código pode conter dígitos, letras latinas e os seguintes símbolos: _-', - ], - 'menuitem' => [ - 'title' => 'Título', - 'editor_title' => 'Editar item', - 'type' => 'Tipo', - 'allow_nested_items' => 'Permitir itens aninhados', - 'allow_nested_items_comment' => 'Itens aninhados podem ser gerados dinamicamente por páginas estáticas e outros tipos de itens', - 'url' => 'URL', - 'reference' => 'Referência', - 'title_required' => 'O título é necessário', - 'unknown_type' => 'Tipo de item desconhecido', - 'unnamed' => 'Item de menu sem nome', - 'add_item' => 'Adicionar item', - 'new_item' => 'Novo item', - 'replace' => 'Substituir este item com seus filhos gerados', - 'replace_comment' => 'Use esta opção para empurrar os itens de menu gerados para o mesmo nível que este item. Este item em si será ocultado.', - 'cms_page' => 'Página CMS', - 'cms_page_comment' => 'Selecione uma página para abrir quando o item for clicado.', - 'reference_required' => 'A referência do item é necessária.', - 'url_required' => 'A URL é necessária', - 'cms_page_required' => 'Por favor, selecione uma página CMS', - 'code' => 'Código', - 'code_comment' => 'Entre com o código do item se deseja acessar com a API.', - ], - 'content' => [ - 'menu_label' => 'Conteúdo', - 'cant_save_to_dir' => 'Não é permitido salvar arquivos de conteúdo no diretório de páginas estáticas.', - ], - 'sidebar' => [ - 'add' => 'Adicionar', - ], - 'object' => [ - 'invalid_type' => 'Tipo de objeto desconhecido', - 'not_found' => 'O objeto requisitado não foi encontrado.', - ], - 'editor' => [ - 'title' => 'Título', - 'new_title' => 'Título da nova página', - 'content' => 'Conteúdo', - 'url' => 'URL', - 'filename' => 'Nome do arquivo', - 'layout' => 'Layout', - 'description' => 'Descrição', - 'preview' => 'Visualizar', - 'enter_fullscreen' => 'Entrar no modo tela cheia', - 'exit_fullscreen' => 'Sair do modo tela cheia', - 'hidden' => 'Ocultar', - 'hidden_comment' => 'Páginas ocultas são acessíveis apenas para administradores.', - 'navigation_hidden' => 'Ocultar na navegação', - 'navigation_hidden_comment' => 'Marque esta opção para ocultar esta página de menus gerados automaticamente e itens de hierarquia de navegação.', - ], - 'snippet' => [ - 'menu_label' => 'Fragmentos', - ], -]; diff --git a/lang/ru/lang.php b/lang/ru/lang.php deleted file mode 100644 index 5aaec692..00000000 --- a/lang/ru/lang.php +++ /dev/null @@ -1,100 +0,0 @@ - [ - 'name' => 'Страницы', - 'description' => 'Страницы и меню.', - ], - 'page' => [ - 'menu_label' => 'Страницы', - 'template_title' => '%s Страницы', - 'delete_confirmation' => 'Вы действительно хотите удалить выбранные страницы? Это также удалит имеющиеся подстраницы.', - 'no_records' => 'Страниц не найдено', - 'delete_confirm_single' => 'Вы действительно хотите удалить эту страницу? Это также удалит имеющиеся подстраницы.', - 'new' => 'Новая страница', - 'add_subpage' => 'Добавить подстраницу', - 'invalid_url' => 'Некорректный формат URL. URL должен начинаться с прямого слеша и может содержать цифры, латинские буквы и следующие символы: _-/.', - 'url_not_unique' => 'Это URL уже используется другой страницей.', - 'layout' => 'Шаблон', - 'layouts_not_found' => 'Шаблоны не найдены', - 'saved' => 'Страница была успешно сохранена.', - 'tab' => 'Страницы', - 'manage_pages' => 'Управление страницами', - 'manage_menus' => 'Управление меню', - 'access_snippets' => 'Доступ к сниппетами', - 'manage_content' => 'Управление содержимым', - ], - 'menu' => [ - 'menu_label' => 'Меню', - 'delete_confirmation' => 'Вы действительно хотите удалить выбранные пункты меню?', - 'no_records' => 'Меню не найдены', - 'new' => 'Новое меню', - 'new_name' => 'Новое меню', - 'new_code' => 'novoe-menyu', - 'delete_confirm_single' => 'Вы действительно хотите удалить это меню?', - 'saved' => 'Меню было успешно сохранено.', - 'name' => 'Имя', - 'code' => 'Код', - 'items' => 'Пункты меню', - 'add_subitem' => 'Добавить подменю', - 'code_required' => 'Поле Код обязательно', - 'invalid_code' => 'Некорректный формат Кода. Код может содержать цифры, латинские буквы и следующие символы: _-/', - ], - 'menuitem' => [ - 'title' => 'Название', - 'editor_title' => 'Редактировать пункт меню', - 'type' => 'Тип', - 'allow_nested_items' => 'Разрешить вложенные', - 'allow_nested_items_comment' => 'Вложенные пункты могут быть динамически сгенерированы статической страницей или другими типами элементов', - 'url' => 'URL', - 'reference' => 'Ссылка', - 'title_required' => 'Название обязательно', - 'unknown_type' => 'Неизвестный тип меню', - 'unnamed' => 'Безымянный пункт', - 'add_item' => 'Добавить пункт (i)', - 'new_item' => 'Новый пункт', - 'replace' => 'Заменять этот пункт его сгенерированными потомками', - 'replace_comment' => 'Отметьте для переноса генерируемых пунктов меню на один уровень с этим пунктом. Сам этот пункт будет скрыт.', - 'cms_page' => 'Страницы CMS', - 'cms_page_comment' => 'Выберите открываемую по клику страницу.', - 'reference_required' => 'Необходима ссылка для пункта меню.', - 'url_required' => 'Необходим URL', - 'cms_page_required' => 'Пожалуйста, выберите страницу CMS', - 'code' => 'Код', - 'code_comment' => 'Введите код пункта меню, если хотите иметь к нему доступ через API.', - ], - 'content' => [ - 'menu_label' => 'Содержимое', - 'cant_save_to_dir' => 'Сохранение файлов содержимого в директорию static-pages запрещено.', - ], - 'sidebar' => [ - 'add' => 'Добавить', - ], - 'object' => [ - 'invalid_type' => 'Неизвестный тип объекта', - 'not_found' => 'Запрашиваемый объект не найден.', - ], - 'editor' => [ - 'title' => 'Название', - 'new_title' => 'Название новой страницы', - 'content' => 'Содержимое', - 'url' => 'URL', - 'filename' => 'Имя Файла', - 'layout' => 'Шаблон', - 'description' => 'Описание', - 'preview' => 'Предпросмотр', - 'enter_fullscreen' => 'Войти в полноэкранный режим', - 'exit_fullscreen' => 'Выйти из полноэкранного режима', - 'hidden' => 'Скрытый', - 'hidden_comment' => 'Скрытые страницы доступны только вошедшим администраторам.', - 'navigation_hidden' => 'Спрятать в навигации', - 'navigation_hidden_comment' => 'Отметьте, чтобы скрыть эту страницу в генерируемых меню и хлебных крошках.', - ], - 'snippet' => [ - 'menu_label' => 'Сниппеты', - ], - 'component' => [ - 'static_page_description' => 'Выводит страницу в CMS шаблоне.', - 'static_menu_description' => 'Выводит меню в CMS шаблоне.', - 'static_menu_menu_code' => 'Укажите код меню, которое должно быть показано', - 'static_breadcrumbs_description' => 'Выводит хлебные крошки для страницы.', - ], -]; diff --git a/lang/sk/lang.php b/lang/sk/lang.php deleted file mode 100644 index e3977a4e..00000000 --- a/lang/sk/lang.php +++ /dev/null @@ -1,121 +0,0 @@ - [ - 'name' => 'Stránky', - 'description' => 'Funkcie pre správu stránok a menu.', - ], - 'page' => [ - 'menu_label' => 'Stránky', - 'template_title' => '%s Stránky', - 'delete_confirmation' => 'Naozaj chcete odstrániť vybrané stránky? Ak existujú nejaké podstránky, budú taktiež odstránené.', - 'no_records' => 'Neboli nájdené žiadne stránky', - 'delete_confirm_single' => 'Naozaj chcete odstrániť túto stránku? Ak existujú nejaké podstránky, budú taktiež odstránené.', - 'new' => 'Nová stránka', - 'add_subpage' => 'Pridať podstránku', - 'invalid_url' => 'Neplatný formát URL adresy. URL by mala začínať symbolom lomítka a môže obsahovať číslice, latinské písmená a nasledujúce znaky: _- /.', - 'url_not_unique' => 'Túto URL adresu už používa iná stránka.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Žiadne layouty neboli nájdené', - 'saved' => 'Stránka bola úspešne uložená.', - 'tab' => 'Stránky', - 'manage_pages' => 'Správa stránok', - 'manage_menus' => 'Správa menu', - 'access_snippets' => 'Správa snippetov', - 'manage_content' => 'Správa obsahu', - ], - 'menu' => [ - 'menu_label' => 'Menu', - 'delete_confirmation' => 'Naozaj chcete odstrániť vybrané menu?', - 'no_records' => 'Neboli nájdené žiadne položky', - 'new' => 'Nové menu', - 'new_name' => 'Nové menu', - 'new_code' => 'nove-menu', - 'delete_confirm_single' => 'Naozaj chcete odstrániť toto menu?', - 'saved' => 'Menu bolo úspešne uložené', - 'name' => 'Názov', - 'code' => 'Kód', - 'items' => 'Položky menu', - 'add_subitem' => 'Pridať vnorenú položku', - 'code_required' => 'Pole kód je povinné.', - 'invalid_code' => 'Neplatný formát kódu. Kód môže obsahovať číslice, latinské písmená a nasledujúce znaky: _-', - ], - 'menuitem' => [ - 'title' => 'Názov', - 'editor_title' => 'Upraviť položku menu', - 'type' => 'Typ', - 'allow_nested_items' => 'Povoliť vnorené položky', - 'allow_nested_items_comment' => 'Vnorené položky môžu byť automaticky generované statickou stránkou alebo niektorými ďalšími typmi položiek', - 'url' => 'URL adresa', - 'reference' => 'Odkaz', - 'search_placeholder' => 'Prehľadať všetky odkazy...', - 'title_required' => 'Názov je povinný', - 'unknown_type' => 'Neznámy typ položky menu', - 'unnamed' => 'Nepomenovaná položka menu', - 'add_item' => 'Pridať Položku', - 'new_item' => 'Nová položka menu', - 'replace' => 'Nahradiť túto položku jej generovanými vnorenými položkami', - 'replace_comment' => 'Zaškrtnite toto pole pokiaľ si prajete vnorené položky menu posunúť na rovnakú úroveň akú má táto položka. Samotná položka zostane skrytá.', - 'cms_page' => 'CMS stránka', - 'cms_page_comment' => 'Vyberte stránku, ktorá sa má otvoriť po kliknutí na položku v menu.', - 'reference_required' => 'Odkaz na položku menu je povinný.', - 'url_required' => 'Adresa URL je povinná', - 'cms_page_required' => 'Prosím vyberte CMS stránku', - 'display_tab' => 'Zobrazenie', - 'hidden' => 'Skrytá', - 'hidden_comment' => 'Skryť túto položku menu pre celú webovú stránku.', - 'attributes_tab' => 'Vlastnosti', - 'code' => 'Kód', - 'code_comment' => 'Zadajte kód položky menu ak k nej chcete pristupovať prostredníctvom API.', - 'css_class' => 'CSS trieda', - 'css_class_comment' => 'Zadajte názov CSS triedy, ktorá sa ma aplikovať pre túto položku menu.', - 'external_link' => 'Externý odkaz', - 'external_link_comment' => 'Otvoriť odkaz tejto položky menu v novom okne.', - 'static_page' => 'Statická stránka', - 'all_static_pages' => 'Všetky statické stránky', - ], - 'content' => [ - 'menu_label' => 'Obsah', - 'cant_save_to_dir' => 'Ukladanie súborov s obsahom do adresára statických stránok nie je povolené.', - ], - 'sidebar' => [ - 'add' => 'Pridať', - ], - 'object' => [ - 'invalid_type' => 'Neznámy typ objektu', - 'not_found' => 'Požadovaný objekt nebol nájdený.', - ], - 'editor' => [ - 'title' => 'Názov', - 'new_title' => 'Názov novej stránky', - 'content' => 'Obsah', - 'url' => 'URL adresa', - 'filename' => 'Názov súboru', - 'layout' => 'Layout', - 'description' => 'Popis', - 'preview' => 'Náhľad', - 'enter_fullscreen' => 'Zapnúť režim celej obrazovky', - 'exit_fullscreen' => 'Vypnúť režim celej obrazovky', - 'hidden' => 'Skrytá', - 'hidden_comment' => 'Skryté stránky sú prístupné iba prihláseným používateľom.', - 'navigation_hidden' => 'Skryť v menu', - 'navigation_hidden_comment' => 'Začiarknutím tohto poľa skryjete túto stránku z automaticky generovaných menu a navigačnej cesty.', - ], - 'snippet' => [ - 'menu_label' => 'Snippety', - ], - 'component' => [ - 'static_page_name' => 'Statická stránka', - 'static_page_description' => 'Zobrazí obsah statickej stránky', - 'static_page_use_content_name' => 'Použiť pole obsah stránky', - 'static_page_use_content_description' => 'Ak nie je začiarknuté, sekcia obsahu sa nezobrazí pri úprave statickej stránky. Obsah stránky bude určený výhradne prostredníctvom zástupcov a premenných.', - 'static_page_default_name' => 'Predvolený layout', - 'static_page_default_description' => 'Nastaví tento layout ako predvolený pre nové stránky', - 'static_page_child_layout_name' => 'Layout podstránky', - 'static_page_child_layout_description' => 'Layout ktorý sa má použiť ako predvolený pre všetky nové podstránky', - 'static_menu_name' => 'Statické menu', - 'static_menu_description' => 'Zobrazí menu na stránke', - 'static_menu_code_name' => 'Menu', - 'static_menu_code_description' => 'Zadajte kód menu, ktoré má komponent zobraziť.', - 'static_breadcrumbs_name' => 'Statická navigačná cesta', - 'static_breadcrumbs_description' => 'Zobrazí navigačnú cestu na stránke.', - ], -]; diff --git a/lang/sl/lang.php b/lang/sl/lang.php deleted file mode 100644 index ed24298e..00000000 --- a/lang/sl/lang.php +++ /dev/null @@ -1,125 +0,0 @@ - [ - 'name' => 'Strani', - 'description' => 'Ustvarjanje strani in menijev.', - ], - 'page' => [ - 'menu_label' => 'Strani', - 'template_title' => '%s strani', - 'delete_confirmation' => 'Ali ste prepričani, da želite izbrisati izbrane strani? S tem boste izbrisali tudi njihove podstrani, če obstajajo.', - 'no_records' => 'Ni najdenih strani.', - 'delete_confirm_single' => 'Ali ste prepričani, da želite izbrisati to stran? S tem boste izbrisali tudi njene podstrani, če obstajajo.', - 'new' => 'Nova stran', - 'add_subpage' => 'Dodaj podstran', - 'invalid_url' => 'Neveljavna oblika URL formata. URL se mora začeti z znakom za desno poševnico in lahko vsebuje številke, latinične črke in naslednje znake: _-/.', - 'url_not_unique' => 'To URL povezavo uporablja že ena od drugih strani.', - 'layout' => 'Postavitev', - 'layouts_not_found' => 'Ni najdenih postavitev.', - 'saved' => 'Stran je bila uspešno shranjena.', - 'tab' => 'Strani', - 'manage_pages' => 'Upravljanje statičnih strani', - 'manage_menus' => 'Upravljanje statičnih menijev', - 'access_snippets' => 'Dostop do gradnikov', - 'manage_content' => 'Upravljanje statičnih vsebin', - ], - 'menu' => [ - 'menu_label' => 'Meniji', - 'delete_confirmation' => 'Ali ste prepričani, da želite izbrisati izbrane menije?', - 'no_records' => 'Ni najdenih menijev.', - 'new' => 'Nov meni', - 'new_name' => 'Nov meni', - 'new_code' => 'nov-meni', - 'delete_confirm_single' => 'Ali ste prepričani, da želite izbrisati ta meni?', - 'saved' => 'Meni je uspešno shranjen.', - 'name' => 'Ime', - 'code' => 'Koda', - 'items' => 'Elementi menija', - 'add_subitem' => 'Dodaj pod-element', - 'code_required' => 'Koda je obvezna.', - 'invalid_code' => 'Neveljaven format kode. Koda lahko vsebuje številke, latinične črke in naslednje znake: _-', - ], - 'menuitem' => [ - 'title' => 'Naslov', - 'editor_title' => 'Element menija', - 'type' => 'Vrsta', - 'allow_nested_items' => 'Dovoli gnezdene elemente', - 'allow_nested_items_comment' => 'Gnezdene elemente lahko dinamično ustvarijo statične strani in nekatere druge vrste elementov.', - 'url' => 'URL', - 'reference' => 'Referenca', - 'search_placeholder' => 'Išči po vseh referencah...', - 'title_required' => 'Naslov je obvezen', - 'unknown_type' => 'Neznana vrsta elementa menija.', - 'unnamed' => 'Neimenovan element menija.', - 'add_item' => 'Dodaj element', - 'new_item' => 'Nov element menija', - 'replace' => 'Zamenjaj ta element z njegovimi pod-elementi', - 'replace_comment' => 'Z uporabo tega kvadratka lahko potisnete ustvarjene elemente menija na njegov nivo, ob tem pa bo le-ta element postal skrit.', - 'cms_page' => 'CMS stran', - 'cms_page_comment' => 'Izberite stran, ki naj se odpre ob kliku na element menija.', - 'reference_required' => 'Referenca elementa menija je obvezna.', - 'url_required' => 'Povezava URL je obvezna.', - 'cms_page_required' => 'Prosimo, izberite CMS stran.', - 'display_tab' => 'Prikaz', - 'hidden' => 'Skrito', - 'hidden_comment' => 'Element menija na spletni strani naj ne bo prikazan.', - 'attributes_tab' => 'Atributi', - 'code' => 'Koda', - 'code_comment' => 'Vnesite kodo za element menija, če želite do njega omogočiti API dostop.', - 'css_class' => 'CSS razred', - 'css_class_comment' => 'Vnesite ime CSS razreda, če želite elementu omogočiti videz po meri.', - 'external_link' => 'Zunanja povezava', - 'external_link_comment' => 'Povezava za ta element menija naj se odpre v novem oknu.', - 'static_page' => 'Statična stran', - 'all_static_pages' => 'Vse statične strani', - ], - 'content' => [ - 'menu_label' => 'Vsebine', - 'saved' => 'Vsebina je bila uspešno shranjena.', - 'cant_save_to_dir' => 'Shranjevanje datotek z vsebino v mapo statičnih strani ni dovoljeno.', - ], - 'sidebar' => [ - 'add' => 'Dodaj', - ], - 'object' => [ - 'invalid_type' => 'Neznana vrsta objekta', - 'unauthorized_type' => 'Nimate pooblastil za upravljanje :type objektov.', - 'not_found' => 'Zahtevanega objekta ni mogoče najti.', - ], - 'editor' => [ - 'title' => 'Naslov', - 'new_title' => 'Nov naslov strani', - 'content' => 'Vsebina', - 'url' => 'URL', - 'filename' => 'Ime datoteke', - 'layout' => 'Postavitev', - 'description' => 'Opis', - 'preview' => 'Predogled', - 'enter_fullscreen' => 'Celozaslonski način', - 'exit_fullscreen' => 'Zapri celozaslonski način', - 'hidden' => 'Skrita stran', - 'hidden_comment' => 'Skrite strani so dostopne le prijavljenim administratorjem.', - 'navigation_hidden' => 'Skrita v navigaciji', - 'navigation_hidden_comment' => 'Skrite strani v navigaciji se v menijih in povezavah ne prikažejo.', - ], - 'snippet' => [ - 'menu_label' => 'Gradniki', - ], - 'component' => [ - 'static_page_name' => 'Statična stran', - 'static_page_description' => 'Ustvari statično stran na CMS postavitvi.', - 'static_page_use_content_name' => 'Uporabi polje z vsebino strani', - 'static_page_use_content_description' => 'Če ni označeno, se razdelek z vsebino pri urejanju statične strani ne bo prikazal. Vsebina strani bo določena izključno prek vsebinskih okvirov in spremenljivk.', - 'static_page_default_name' => 'Privzeta postavitev', - 'static_page_default_description' => 'To postavitev definira kot privzeto za nove strani.', - 'static_page_child_layout_name' => 'Postavitev podstrani', - 'static_page_child_layout_description' => 'To postavitev definira kot privzeto za vse nove podstrani.', - 'static_menu_name' => 'Statični meni', - 'static_menu_description' => 'Ustvari meni na CMS postavitvi.', - 'static_menu_code_name' => 'Meni', - 'static_menu_code_description' => 'Določite kodo menija, ki ga mora sestaviti komponenta.', - 'static_breadcrumbs_name' => 'Statične povezave', - 'static_breadcrumbs_description' => 'Ustvari povezave za statično stran.', - 'child_pages_name' => 'Podstrani', - 'child_pages_description' => 'Prikaže seznam podstrani za trenutno stran.', - ], -]; diff --git a/lang/sv/lang.php b/lang/sv/lang.php deleted file mode 100644 index 496e468d..00000000 --- a/lang/sv/lang.php +++ /dev/null @@ -1,94 +0,0 @@ - [ - 'name' => 'Sidor', - 'description' => 'Sidor & menyer.', - ], - 'page' => [ - 'menu_label' => 'Sidor', - 'template_title' => '%s Sidor', - 'delete_confirmation' => 'Vill du verkligen ta bort de valda sidorna? Detta kommer också ta bort undersidorna, ifall det finns några.', - 'no_records' => 'Inga sidor hittades', - 'delete_confirm_single' => 'Vill du verkligen ta bort den valda sidan? Detta kommer också ta bort sidans undersidor, ifall det finns några.', - 'new' => 'Ny sida', - 'add_subpage' => 'Lägg till undersida', - 'invalid_url' => 'Ogiltigt format på URL. URL:en ska börja med slash och kan innehålla siffror, latinska bokstäver och följande symboler: _-/', - 'url_not_unique' => 'Denna URL används redan av en annan sida.', - 'layout' => 'Layout', - 'layouts_not_found' => 'Layouter kan inte hittas', - 'saved' => 'Sidan har sparats.', - 'tab' => 'Sidor', - 'manage_pages' => 'Hantera statiska sidor', - 'manage_menus' => 'Hantera statiska menyer', - 'access_snippets' => 'Hantera stumpar', - 'manage_content' => 'Hantera statiskt innehåll', - ], - 'menu' => [ - 'menu_label' => 'Menyer', - 'delete_confirmation' => 'Vill du verkligen ta bort valda de menyerna?', - 'no_records' => 'Inga föremål kunde finnas', - 'new' => 'Ny meny', - 'new_name' => 'Ny meny', - 'new_code' => 'ny-meny', - 'delete_confirm_single' => 'Vill du verkligen ta bort denna menyn?', - 'saved' => 'Menyn har sparats.', - 'name' => 'Namn', - 'code' => 'Kod', - 'items' => 'Menyföremål', - 'add_subitem' => 'Lägg till underföremål', - 'code_required' => 'Koden är obligatorisk', - 'invalid_code' => 'Ogiltigt kodformat. Koden kan innehålla siffror, latinska bokstäver och följande symboler: _-', - ], - 'menuitem' => [ - 'title' => 'Rubrik', - 'editor_title' => 'Redigera menyföremål', - 'type' => 'Typ', - 'allow_nested_items' => 'Tillåt underliggande föremål', - 'allow_nested_items_comment' => 'Underliggande föremål kan skapas dynamiskt av en etatisk sida och några andra föremålstyper', - 'url' => 'URL', - 'reference' => 'Referens', - 'title_required' => 'Rubriken är obligatorisk', - 'unknown_type' => 'Okänd menyföremålstyp', - 'unnamed' => 'Namnlöst menyföremål', - 'add_item' => 'Lägg till föremål', - 'new_item' => 'Nytt menyföremål', - 'replace' => 'Ersätt detta föremål med dens skapade underföremål', - 'replace_comment' => 'Använd denna kryssruta för att föra skapade menyföremål till samma nivå som detta föremålet. Detta föremålet kommer att gömmas.', - 'cms_page' => 'CMS-sida', - 'cms_page_comment' => 'Välj en sida som ska öppnas när menyföremålet klickas.', - 'reference_required' => 'Menyföremålets referens är obligatorisk.', - 'url_required' => 'URL:en är obligatorisk', - 'cms_page_required' => 'Vänligen välj en CMS-sida', - 'code' => 'Kod', - 'code_comment' => 'Fyll i menyföremålets kod om du vill få tillgång till det i API:t.', - ], - 'content' => [ - 'menu_label' => 'Innehåll', - 'cant_save_to_dir' => 'Att sparar innehållsfiler till mappen för statiska sidor är inte tillåtet.', - ], - 'sidebar' => [ - 'add' => 'Lägg till', - ], - 'object' => [ - 'invalid_type' => 'Ogiltig objekttyp', - 'not_found' => 'Det begärda objektet kunde inte finnas.', - ], - 'editor' => [ - 'title' => 'Rubrik', - 'new_title' => 'Ny sidrubrik', - 'content' => 'Innehåll', - 'url' => 'URL', - 'filename' => 'Filnamn', - 'layout' => 'Layout', - 'description' => 'Beskrivning', - 'preview' => 'Förhandsgranska', - 'enter_fullscreen' => 'Gå in i fullskärmsläge', - 'exit_fullscreen' => 'Gå ut ur fullskärmsläge', - 'hidden' => 'Gömd', - 'hidden_comment' => 'Gömda sidor är bara tillgängliga för inloggande back-endanvändare.', - 'navigation_hidden' => 'Göm i navigation', - 'navigation_hidden_comment' => 'Fyll i denna rutan för att gömma sidan från automatiskt skapade menyer och sökvägar.', - ], - 'snippet' => [ - 'menu_label' => 'Stumpar', - ], -]; diff --git a/lang/tr/lang.php b/lang/tr/lang.php deleted file mode 100644 index f910697b..00000000 --- a/lang/tr/lang.php +++ /dev/null @@ -1,121 +0,0 @@ - [ - 'name' => 'Sayfalar', - 'description' => 'Sayfalar & menüler modülü.', - ], - 'page' => [ - 'menu_label' => 'Sayfalar', - 'template_title' => '%s Sayfalar', - 'delete_confirmation' => 'Seçili sayfaları silmek istiyor musunuz? Alt sayfalar da silinecektir.', - 'no_records' => 'Sayfa bulunamadı', - 'delete_confirm_single' => 'Bu sayfayı silmek istiyor musunuz? Alt sayfalar da silinecektir', - 'new' => 'Yeni sayfa', - 'add_subpage' => 'Altsayfa ekle', - 'invalid_url' => 'Geçersiz URL formatı. URL eğik çizgi sembolü ile başlamalıdır ve rakam, latin harfleri ve bu sembolleri: _-/. içerebilir.', - 'url_not_unique' => 'Bu URL başka bir sayfa tarafından kullanılıyor', - 'layout' => 'Şablon', - 'layouts_not_found' => 'Şablon bulunamadı', - 'saved' => 'Sayfa başarıyla kaydedildi.', - 'tab' => 'Sayfalar', - 'manage_pages' => 'Sayfaları yönetebilsin', - 'manage_menus' => 'Menüleri yönetebilsin', - 'access_snippets' => 'Snippetleri yönetebilsin', - 'manage_content' => 'Sabit içerikleri yönetebilsin', - ], - 'menu' => [ - 'menu_label' => 'Menüler', - 'delete_confirmation' => 'Seçili menüleri silmek istiyor musunuz?', - 'no_records' => 'Menü bulunamadı', - 'new' => 'Yeni Menü', - 'new_name' => 'Yeni menü', - 'new_code' => 'yeni-menu', - 'delete_confirm_single' => 'Bu menüyü silmek istiyor musunuz?', - 'saved' => 'Menü başarıyla kaydedildi.', - 'name' => 'İsim', - 'code' => 'Kod', - 'items' => 'Menü Ögeleri', - 'add_subitem' => 'Altöge ekle', - 'code_required' => 'Kod gerekli', - 'invalid_code' => 'Geçersiz KOD formatı. Kod yalnızca rakam, Latin harfleri ve bu sembolleri: _- içerebilir.', - ], - 'menuitem' => [ - 'title' => 'Başlık', - 'editor_title' => 'Menü Ögesini Düzenle', - 'type' => 'Tür', - 'allow_nested_items' => 'İçiçe ögelere izin ver', - 'allow_nested_items_comment' => 'İç içe öğeler statik sayfa ve bazı diğer öğe türlerine göre dinamik olarak üretilen olabilir', - 'url' => 'URL', - 'reference' => 'Referans', - 'search_placeholder' => 'Referansları ara...', - 'title_required' => 'Başlık gerekli', - 'unknown_type' => 'Geçersiz menü ögesi türü', - 'unnamed' => 'İsimsiz menü ögesi', - 'add_item' => 'Öge Ekle', - 'new_item' => 'Yeni menü ögesi', - 'replace' => 'Bu ögeyi oluşturulan çocuklarıyla değiştir', - 'replace_comment' => 'Use this checkbox to push generated menu items to the same level with this item. This item itself will be hidden.', - 'cms_page' => 'CMS Sayfası', - 'cms_page_comment' => 'Menü ögesine tıklandığında açılacak sayfayı seçin', - 'reference_required' => 'Menü ögesi referansı gereklidir.', - 'url_required' => 'URL gereklidir', - 'cms_page_required' => 'Lütfen bir CMS sayfası seçin', - 'display_tab' => 'Görünüm', - 'hidden' => 'Gizli', - 'hidden_comment' => 'Bu menüyü önyüzde gizle.', - 'attributes_tab' => 'Öznitellikler', - 'code' => 'Kod', - 'code_comment' => 'API ile giriş yapabilmek için menü ögesi kodunu girin.', - 'css_class' => 'CSS Class', - 'css_class_comment' => 'Bu menüye özel bir görünüm vermek için bir CSS sınıfı adı girin.', - 'external_link' => 'Dış link', - 'external_link_comment' => 'Bu menü için bağlantıları yeni sekmede aç.', - 'static_page' => 'Sayfa', - 'all_static_pages' => 'Tüm sayfalar', - ], - 'content' => [ - 'menu_label' => 'İçerik', - 'cant_save_to_dir' => 'Statik sayfalar dizinine içerik dosyalarını kaydetme izni verilmez.', - ], - 'sidebar' => [ - 'add' => 'Ekle', - ], - 'object' => [ - 'invalid_type' => 'Bilineyen nesne türü', - 'not_found' => 'İstenen nesne bulunamadı', - ], - 'editor' => [ - 'title' => 'Başlık', - 'new_title' => 'Yeni sayfa başlığı', - 'content' => 'İçerik', - 'url' => 'URL', - 'filename' => 'Dosya Adı', - 'layout' => 'Layout', - 'description' => 'Tanımlama', - 'preview' => 'Önizleme', - 'enter_fullscreen' => 'Tam Ekran moduna geç', - 'exit_fullscreen' => 'Tam Ekran modundan çık', - 'hidden' => 'Gizli', - 'hidden_comment' => 'Gizli sayfalar yalnızca yönetim paneline giriş yapmış kullanıcılar tarafından görüntülenebilir.', - 'navigation_hidden' => 'Menüde Gizle', - 'navigation_hidden_comment' => 'Otomatik olarak oluşturulan menüler ve kırıntıları gizlemek için bu kutuyu işaretleyin.', - ], - 'snippet' => [ - 'menu_label' => 'Snippetlar', - ], - 'component' => [ - 'static_page_name' => 'Sabit sayfa', - 'static_page_description' => 'CMS bölümüne sabit sayfa içeriği ekler.', - 'static_page_use_content_name' => 'Sayfa içeriği alanını kullan', - 'static_page_use_content_description' => 'Seçilmezse, statik sayfa düzenlenirken içerik bölümü görünmez. Sayfa içeriği yalnızca placeholderlar ve değişkenler aracılığıyla belirlenir.', - 'static_page_default_name' => 'Varsayılan şablon', - 'static_page_default_description' => 'Bu şablonu yeni sayfalar için varsayılan olarak tanımlar.', - 'static_page_child_layout_name' => 'Alt sayfa şablobu', - 'static_page_child_layout_description' => 'Yeni alt sayfalar için varsayılan olarak kullanılacak şablon', - 'static_menu_name' => 'Statik (sabit) menü', - 'static_menu_description' => 'CMS bölümüne sabit menü içeriği ekler.', - 'static_menu_code_name' => 'Menü', - 'static_menu_code_description' => 'Component in göstereceği menünün kodunu belirtin.', - 'static_breadcrumbs_name' => 'Sabit breadcrumbs', - 'static_breadcrumbs_description' => 'Sabit sayfaya breadcrumbs ekler.', - ], -]; diff --git a/lang/uk/lang.php b/lang/uk/lang.php deleted file mode 100644 index 6d2f50c5..00000000 --- a/lang/uk/lang.php +++ /dev/null @@ -1,100 +0,0 @@ - [ - 'name' => 'Сторінки', - 'description' => 'Сторінки і меню.', - ], - 'page' => [ - 'menu_label' => 'Сторінки', - 'template_title' => '%s Сторінки', - 'delete_confirmation' => 'Ви дійсно хочете видалити вибрані сторінки? Це також видалить наявні підсторінки.', - 'no_records' => 'Сторінок не знайдено', - 'delete_confirm_single' => 'Ви дійсно хочете видалити цю сторінку? Це також видалить наявні підсторінки.', - 'new' => 'Нова сторінка', - 'add_subpage' => 'Додати підсторінку', - 'invalid_url' => 'Некоректний формат URL. URL повинен починатися з прямого слеша і може містити цифри, латинські літери і такі символи: _-/.', - 'url_not_unique' => 'Цей URL вже використовується іншою сторінкою.', - 'layout' => 'Шаблон', - 'layouts_not_found' => 'Шаблони не знайдені', - 'saved' => 'Сторінка була успішно збережена.', - 'tab' => 'Сторінки', - 'manage_pages' => 'Управління сторінками', - 'manage_menus' => 'Управління меню', - 'access_snippets' => 'Доступ до сніппетів', - 'manage_content' => 'Управління змістом', - ], - 'menu' => [ - 'menu_label' => 'Меню', - 'delete_confirmation' => 'Ви дійсно хочете видалити вибрані пункти меню?', - 'no_records' => 'Меню не знайдені', - 'new' => 'Нове меню', - 'new_name' => 'Новое меню', - 'new_code' => 'nove-menu', - 'delete_confirm_single' => 'Ви дійсно хочете видалити це меню?', - 'saved' => 'Меню було успішно збережено.', - 'name' => 'Ім`я', - 'code' => 'Код', - 'items' => 'Пункти меню', - 'add_subitem' => 'Додати підменю', - 'code_required' => 'Поле Код обов\'язкове', - 'invalid_code' => 'Некоректний формат Коду. Код може містити цифри, латинські літери і такі символи: _-/', - ], - 'menuitem' => [ - 'title' => 'Назва', - 'editor_title' => 'Редагувати пункт меню', - 'type' => 'Тип', - 'allow_nested_items' => 'Дозволити вкладені', - 'allow_nested_items_comment' => 'Вкладені пункти можуть бути динамічно згенеровані статичною сторінкою або іншими типами елементів', - 'url' => 'URL', - 'reference' => 'Посилання', - 'title_required' => 'Назва обов\'язково', - 'unknown_type' => 'Невідомий тип меню', - 'unnamed' => 'Безіменний пункт', - 'add_item' => 'Додати пункт ( i )', - 'new_item' => 'Новий пункт', - 'replace' => 'Замінювати цей пункт його згенеруванними нащадками', - 'replace_comment' => 'Відмітьте для перенесення згенерованних пунктів меню на один рівень з цим пунктом. Сам цей пункт буде приховано.', - 'cms_page' => 'Сторінки CMS', - 'cms_page_comment' => 'Оберіть відкриваючу при натисканні сторінку.', - 'reference_required' => 'Необхідне посилання для пункту меню.', - 'url_required' => 'Необхідний URL', - 'cms_page_required' => 'Будь ласка, виберіть сторінку CMS', - 'code' => 'Код', - 'code_comment' => 'Введіть код пункту меню, якщо хочете мати до нього доступ через API.', - ], - 'content' => [ - 'menu_label' => 'Зміст', - 'cant_save_to_dir' => 'Збереження файлів змісту в директорію static-pages заборонено.', - ], - 'sidebar' => [ - 'Add' => 'Додати', - ], - 'object' => [ - 'invalid_type' => 'Невідомий тип об\'єкту', - 'not_found' => 'Запитуваний об\'єкт не знайдено.', - ], - 'editor' => [ - 'title' => 'Назва', - 'new_title' => 'Назва нової сторінки', - 'content' => 'Зміст', - 'url' => 'URL', - 'filename' => 'Ім\'я файлу', - 'layout' => 'Шаблон', - 'description' => 'Опис', - 'preview' => 'Попередній огляд', - 'enter_fullscreen' => 'Увійти в повноекранний режим', - 'exit_fullscreen' => 'Вийти з повноекранного режиму', - 'hidden' => 'Прихований', - 'hidden_comment' => 'Приховані сторінки доступні тільки увійшовшим адміністраторам.', - 'navigation_hidden' => 'Сховати в навігації', - 'navigation_hidden_comment' => 'Відмітьте, щоб приховати цю сторінку в генеруючих меню і хлібних крихтах.', - ], - 'snippet' => [ - 'menu_label' => 'Сніппети', - ], - 'component' => [ - 'static_page_description' => 'Виводити сторінку в CMS шаблоні.', - 'static_menu_description' => 'Виводити меню в CMS шаблоні.', - 'static_menu_menu_code' => 'Вкажіть код меню, яке повинно бути показано', - 'static_breadcrumbs_description' => 'Виводити хлібні крихти для сторінки.', - ], -]; diff --git a/lang/zh-cn/lang.php b/lang/zh-cn/lang.php deleted file mode 100644 index 753debe2..00000000 --- a/lang/zh-cn/lang.php +++ /dev/null @@ -1,113 +0,0 @@ - [ - 'name' => '页面', - 'description' => '页面和菜单功能拓展', - ], - 'page' => [ - 'menu_label' => '页面', - 'template_title' => '%s 页面', - 'delete_confirmation' => '你真的要删除所选页面吗? 如果有子页面,也将被删除。', - 'no_records' => '找不到页面', - 'delete_confirm_single' => '你真的要删除这个页面吗? 如果有子页面,也将被删除。', - 'new' => '新建', - 'add_subpage' => '插入子页面', - 'invalid_url' => 'URL 格式无效,应以 \'/\' 开头,可以包含数字,字母和以下符号:_-/', - 'url_not_unique' => 'URL 已存在', - 'layout' => '布局', - 'layouts_not_found' => '找不到布局文件', - 'saved' => '保存成功!', - 'tab' => '页面', - 'manage_pages' => '管理静态页面', - 'manage_menus' => '管理静态菜单', - 'access_snippets' => '代码片段', - 'manage_content' => '管理静态内容', - ], - 'menu' => [ - 'menu_label' => '菜单', - 'delete_confirmation' => '你真的要删除所选菜单吗?', - 'no_records' => '找不到菜单', - 'new' => '新建', - 'new_name' => '未命名的菜单', - 'new_code' => 'new-menu', - 'delete_confirm_single' => '你真的要删除这个菜单吗?', - 'saved' => '保存成功!', - 'name' => '名称', - 'code' => '编码', - 'items' => '菜单项', - 'add_subitem' => '插入子项', - 'code_required' => '编码是必要的!', - 'invalid_code' => '编码格式无效,该编码可以包含数字,字母和以下符号:_-', - ], - 'menuitem' => [ - 'title' => '标题', - 'editor_title' => '编辑菜单项', - 'type' => '类型', - 'allow_nested_items' => '允许嵌套', - 'allow_nested_items_comment' => '嵌套项目可以通过静态页面和其他一些项目类型动态生成', - 'url' => 'URL', - 'reference' => '参考', - 'search_placeholder' => '搜索所有参考...', - 'title_required' => '标题是必需的', - 'unknown_type' => '未知的菜单项类型', - 'unnamed' => '未命名的菜单项', - 'add_item' => '掺入菜单项I', - 'new_item' => '新菜单项', - 'replace' => '将此项目替换为生成的子项', - 'replace_comment' => '使用此复选框产生的菜单项推到与本项目同一层级。 该项目本身将被隐藏。', - 'cms_page' => 'CMS 页面', - 'cms_page_comment' => '当单击菜单项时,选择要打开的页面。', - 'reference_required' => '菜单项参考 是必需的。', - 'url_required' => 'URL 是必需的。', - 'cms_page_required' => '请选择 CMS 页面', - 'code' => '编码', - 'code_comment' => '如果要使用 API 访问菜单项代码,请输入。', - 'static_page' => '静态页面', - 'all_static_pages' => '所有静态页', - ], - 'content' => [ - 'menu_label' => '内容', - 'cant_save_to_dir' => '将内容文件保存到 \'static-pages\' 目录是不允许的。', - ], - 'sidebar' => [ - 'add' => '插入', - ], - 'object' => [ - 'invalid_type' => '未知的对象类型', - 'not_found' => '找不到请求的对象。', - ], - 'editor' => [ - 'title' => '标题', - 'new_title' => '未命名', - 'content' => '内容', - 'url' => 'URL', - 'filename' => '文件名', - 'layout' => '布局', - 'description' => '描述', - 'preview' => '预览', - 'enter_fullscreen' => '全屏编辑', - 'exit_fullscreen' => '退出全屏', - 'hidden' => '隐藏', - 'hidden_comment' => '隐藏的页面只能由登录的后台用户访问。', - 'navigation_hidden' => '在导航中隐藏', - 'navigation_hidden_comment' => '选中此框可将该页面在自动生成的菜单和面包屑导航中隐藏起来。', - ], - 'snippet' => [ - 'menu_label' => '代码片段', - ], - 'component' => [ - 'static_page_name' => '静态页', - 'static_page_description' => '在 CMS 布局中输出静态页。', - 'static_page_use_content_name' => '使用页面内容字段', - 'static_page_use_content_description' => '如果未选中,编辑静态页面时内容部分将不会出现。 页面内容将仅通过占位符和变量来确定。', - 'static_page_default_name' => '默认布局', - 'static_page_default_description' => '将此布局定义为新页面的默认设置', - 'static_page_child_layout_name' => '子页面布局', - 'static_page_child_layout_description' => '该布局用作任何新子页面的默认布局', - 'static_menu_name' => '静态菜单', - 'static_menu_description' => '输出 CMS 布局中的菜单。', - 'static_menu_code_name' => '菜单', - 'static_menu_code_description' => '指定组件应输出的菜单代码。', - 'static_breadcrumbs_name' => '静态面包屑导航', - 'static_breadcrumbs_description' => '输出静态页面的面包屑导航。', - ], -]; diff --git a/rainlab-pages.mix.js b/rainlab-pages.mix.js deleted file mode 100644 index 6d1227d4..00000000 --- a/rainlab-pages.mix.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - |-------------------------------------------------------------------------- - | Mix Asset Management - |-------------------------------------------------------------------------- - | - | Mix provides a clean, fluent API for defining some Webpack build steps - | for your theme assets. By default, we are compiling the CSS - | file for the application as well as bundling up all the JS files. - | - */ - -module.exports = (mix) => { - mix.less('plugins/rainlab/pages/assets/less/pages.less', 'plugins/rainlab/pages/assets/css/'); - mix.less('plugins/rainlab/pages/assets/less/treeview.less', 'plugins/rainlab/pages/assets/css/'); -} diff --git a/updates/version.yaml b/updates/version.yaml index 736e5da5..7b4bbc6b 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -1,91 +1 @@ -v1.0.1: Implemented the static pages management and the Static Page component. -v1.0.2: Fixed the page preview URL. -v1.0.3: Implemented menus. -v1.0.4: Implemented the content block management and placeholder support. -v1.0.5: Added support for the Sitemap plugin. -v1.0.6: Minor updates to the internal API. -v1.0.7: Added the Snippets feature. -v1.0.8: Minor improvements to the code. -v1.0.9: Fixes issue where Snippet tab is missing from the Partials form. -v1.0.10: Add translations for various locales. -v1.0.11: Fixes issue where placeholders tabs were missing from Page form. -v1.0.12: Implement Media Manager support. -v1.1.0: Adds meta title and description to pages. Adds |staticPage filter. -v1.1.1: Add support for Syntax Fields. -v1.1.2: Static Breadcrumbs component now respects the hide from navigation setting. -v1.1.3: Minor back-end styling fix. -v1.1.4: Minor fix to the StaticPage component API. -v1.1.5: Fixes bug when using syntax fields. -v1.1.6: Minor styling fix to the back-end UI. -v1.1.7: Improved menu item form to include CSS class, open in a new window and hidden flag. -v1.1.8: Improved the output of snippet partials when saved. -v1.1.9: Minor update to snippet inspector internal API. -v1.1.10: Fixes a bug where selecting a layout causes permanent unsaved changes. -v1.1.11: Add support for repeater syntax field. -v1.2.0: Added support for translations, UI updates. -v1.2.1: Use nice titles when listing the content files. -v1.2.2: Minor styling update. -v1.2.3: Snippets can now be moved by dragging them. -v1.2.4: Fixes a bug where the cursor is misplaced when editing text files. -v1.2.5: Fixes a bug where the parent page is lost upon changing a page layout. -v1.2.6: Shared view variables are now passed to static pages. -v1.2.7: Fixes issue with duplicating properties when adding multiple snippets on the same page. -v1.2.8: Fixes a bug where creating a content block without extension doesn't save the contents to file. -v1.2.9: Add conditional support for translating page URLs. -v1.2.10: Streamline generation of URLs to use the new Cms::url helper. -v1.2.11: Implements repeater usage with translate plugin. -v1.2.12: Fixes minor issue when using snippets and switching the application locale. -v1.2.13: Fixes bug when AJAX is used on a page that does not yet exist. -v1.2.14: Add theme logging support for changes made to menus. -v1.2.15: Back-end navigation sort order updated. -v1.2.16: Fixes a bug when saving a template that has been modified outside of the CMS (mtime mismatch). -v1.2.17: Changes locations of custom fields to secondary tabs instead of the primary Settings area. New menu search ability on adding menu items -v1.2.18: Fixes cache-invalidation issues when RainLab.Translate is not installed. Added Greek & Simplified Chinese translations. Removed deprecated calls. Allowed saving HTML in snippet properties. Added support for the MediaFinder in menu items. -v1.2.19: Catch exception with corrupted menu file. -v1.2.20: StaticMenu component now exposes menuName property; added pages.menu.referencesGenerated event. -v1.2.21: Fixes a bug where last Static Menu item cannot be deleted. Improved Persian, Slovak and Turkish translations. -v1.3.0: Added support for using Database-driven Themes when enabled in the CMS configuration. -v1.3.1: Added ChildPages Component, prevent hidden pages from being returned via menu item resolver. -v1.3.2: Fixes error when creating a subpage whose parent has no layout set. -v1.3.3: Improves user experience for users with only partial access through permissions -v1.3.4: Fix error where large menus were being truncated due to the PHP "max_input_vars" configuration value. Improved Slovenian translation. -v1.3.5: Minor fix to bust the browser cache for JS assets. Prevent duplicate property fields in snippet inspector. -v1.3.6: ChildPages component now displays localized page titles from Translate plugin. -v1.3.7: Adds MenuPicker formwidget. Adds future support for v2.0 of October CMS. -v1.4.0: Fixes bug when adding menu items in October CMS v2.0. -v1.4.1: Fixes support for configuration values. -v1.4.3: Fixes page deletion is newer platform builds. -v1.4.4: Disable touch device detection -v1.4.5: Minor styling improvements -v1.4.6: Minor styling improvements -v1.4.7: Minor layout fix in the Page editor -v1.4.8: Fixes rich editor usage inside repeaters. Adds getProcessedMarkup event. -v1.4.9: Fixes a lifecycle issue when switching the page layout. -v1.4.10: Fixes maintenance mode when using static pages. -v1.4.11: Adds type hidden to content placeholders. -v1.4.12: Improve support with October v2.2 -v1.5.0: Improve support with October v3.0 -v1.5.4: Compatibility updates -v1.5.5: Fixes media finder added to menu in October v2 -v1.5.6: Fixes concurrency save form in October v3 -v1.5.7: Adds page finder support for October v3.2 -v1.5.8: Fixes resolving links used in static pages -v1.5.9: Fixes fancy layout with nested forms -v1.5.10: Adds forward compatibility with October CMS v3.4 -v1.5.12: Fixes more areas of forward compatibility -v2.0.1: Compatibility with core snippets -v2.0.2: Fixes bug opening menu with tailor list menu items -v2.0.3: Fixes bug showing wrong placeholder code -v2.0.4: Fixes sidebar delete button in October v3.5 -v2.0.5: Compatibility fixes for October v3.6 -v2.1.0: Tabs are now stored and reopened on page load -v2.1.1: Fixes index page spamming event log -v2.1.2: Fixes clearing cache -v2.1.3: Compatibility fixes for October v3.7 -v2.2.0: Adds sort configuration to page list -v2.2.1: Compatibility fixes for October v4.2 -v2.2.2: Fixes translatable globe showing for fields -v2.2.3: Fixes placeholder tab missing from the Static Page editor on Twig 3.12+ -v2.2.4: Fixes tab rendering in Firefox -v2.2.5: Fixes performance of tree control UI -v2.2.6: Compatibility fixes for October v4.3 +v3.0.0: Modernized backend editor rebuilt on the Vue Editor module. File-based data model and frontend components unchanged for drop-in compatibility. diff --git a/vuecomponents/ContentEditor.php b/vuecomponents/ContentEditor.php new file mode 100644 index 00000000..cbc7c74f --- /dev/null +++ b/vuecomponents/ContentEditor.php @@ -0,0 +1,21 @@ + this.$refs.editor.layout()); + } + if (this.$refs.markdownEditor) { + this.$nextTick(() => this.$refs.markdownEditor.refresh()); + } + }, + + documentCreatedOrLoaded: function() { + this.buildMarkupModel(); + } + }, + watch: { + isRicheditorDocument: function(value) { + if (!value) { + this.toolbarExtensionPoint = []; + } + }, + isMarkdownDocument: function(value) { + if (!value) { + this.toolbarExtensionPoint = []; + } + } + } +}; diff --git a/vuecomponents/contenteditor/partials/_contenteditor.php b/vuecomponents/contenteditor/partials/_contenteditor.php new file mode 100644 index 00000000..99c5fe57 --- /dev/null +++ b/vuecomponents/contenteditor/partials/_contenteditor.php @@ -0,0 +1,70 @@ + + + + + + + diff --git a/vuecomponents/menueditor/assets/js/menueditor.js b/vuecomponents/menueditor/assets/js/menueditor.js new file mode 100644 index 00000000..6433e4e8 --- /dev/null +++ b/vuecomponents/menueditor/assets/js/menueditor.js @@ -0,0 +1,518 @@ +import { DocumentComponentBase } from '../../../../../../../modules/editor/assets/js/editor.extension.documentcomponent.base.js'; + +export default { + extends: DocumentComponentBase, + data: function() { + return { + documentSettingsPopupTitle: this.trans('Menu') || 'Menu', + items: [], + selectedItem: null, + itemFormLoaded: false, + modalVisible: false, + newItemTitle: this.trans('New menu item'), + nextItemId: 1, + dragItemId: null, + dropTargetId: null + }; + }, + computed: { + flatItems: function() { + // Flatten the nested tree into a list with depth for indented rendering. + const result = []; + const walk = (list, depth, parent) => { + list.forEach((item) => { + result.push({ item: item, depth: depth, parent: parent, siblings: list }); + if (item._children && item._children.length) { + walk(item._children, depth + 1, item); + } + }); + }; + walk(this.items, 0, null); + return result; + }, + + toolbarElements: function() { + return [ + { + type: 'button', + icon: 'icon-save-cloud', + label: this.trans('backend::lang.form.save'), + hotkey: 'ctrl+s, cmd+s', + tooltip: this.trans('backend::lang.form.save'), + command: 'save' + }, + { + type: 'button', + icon: 'icon-settings', + label: this.trans('editor::lang.common.settings'), + command: 'settings', + hidden: !this.hasSettingsForm + }, + { + type: 'button', + icon: 'icon-plus', + label: this.trans('Add item'), + command: 'add-item' + }, + { + type: 'separator' + }, + { + type: 'button', + icon: 'icon-delete', + disabled: this.isNewDocument, + command: 'delete', + hotkey: 'shift+option+d', + tooltip: this.trans('backend::lang.form.delete') + }, + { + type: 'button', + icon: this.documentHeaderCollapsed ? 'icon-angle-down' : 'icon-angle-up', + command: 'document:toggleToolbar', + fixedRight: true, + tooltip: this.trans('editor::lang.common.toggle_document_header') + } + ]; + } + }, + methods: { + getRootProperties: function() { + return ['code', 'items']; + }, + + getMainUiDocumentProperties: function() { + return ['name', 'code', 'items']; + }, + + getSaveDocumentData: function(inspectorDocumentData) { + const rootProperties = this.getRootProperties(); + const documentData = inspectorDocumentData ? inspectorDocumentData : this.documentData; + const data = $.oc.vueUtils.getCleanObject(documentData); + const result = { settings: {} }; + const ignoredProperties = ['items']; + + Object.keys(data).forEach((property) => { + if (property === 'settings' || ignoredProperties.indexOf(property) !== -1) { + return; + } + if (rootProperties.indexOf(property) !== -1) { + result[property] = data[property]; + } + else { + result.settings[property] = data[property]; + } + }); + + if (typeof data.settings === 'object' && data.settings !== null) { + Object.keys(data.settings).forEach((property) => { + if (rootProperties.indexOf(property) === -1 && result.settings[property] === undefined) { + result.settings[property] = data.settings[property]; + } + }); + } + + result.items = this.serializeItems(this.items); + + return result; + }, + + syncItemsToDocument: function() { + if (this.documentData) { + this.documentData.items = this.serializeItems(this.items); + } + }, + + serializeItems: function(items) { + return items.map((item) => { + const copy = Object.assign({}, item); + delete copy._children; + delete copy._selected; + delete copy._id; + delete copy.typeLabel; + if (item._children && item._children.length) { + copy.items = this.serializeItems(item._children); + } + else { + delete copy.items; + } + return copy; + }); + }, + + inflateItems: function(rawItems) { + return (rawItems || []).map((raw) => { + const item = Object.assign({}, raw); + item._id = this.nextItemId++; + item._children = this.inflateItems(raw.items || []); + item._selected = false; + delete item.items; + return item; + }); + }, + + // Subtitle shown under a menu item row (e.g. "Static page"). + itemSubtitle: function(item) { + return item.typeLabel || item.type || ''; + }, + + newBlankItem: function() { + return { + _id: this.nextItemId++, + title: this.newItemTitle, + type: 'url', + typeLabel: 'URL', + url: '/', + code: '', + reference: '', + cmsPage: '', + nesting: false, + replace: false, + viewBag: {}, + _children: [], + _selected: false + }; + }, + + addItem: function() { + const item = this.newBlankItem(); + this.items.push(item); + this.syncItemsToDocument(); + this.editItem(item); + }, + + deleteItem: function(item, list) { + const arr = list || this.items; + const idx = arr.indexOf(item); + if (idx !== -1) { + arr.splice(idx, 1); + if (this.selectedItem === item) { + this.selectedItem = null; + } + this.syncItemsToDocument(); + } + }, + + moveItemUp: function(entry) { + const arr = entry.siblings; + const idx = arr.indexOf(entry.item); + if (idx > 0) { + arr.splice(idx, 1); + arr.splice(idx - 1, 0, entry.item); + this.syncItemsToDocument(); + } + }, + + moveItemDown: function(entry) { + const arr = entry.siblings; + const idx = arr.indexOf(entry.item); + if (idx !== -1 && idx < arr.length - 1) { + arr.splice(idx, 1); + arr.splice(idx + 1, 0, entry.item); + this.syncItemsToDocument(); + } + }, + + indentItem: function(entry) { + const arr = entry.siblings; + const idx = arr.indexOf(entry.item); + if (idx > 0) { + arr.splice(idx, 1); + arr[idx - 1]._children.push(entry.item); + this.syncItemsToDocument(); + } + }, + + outdentItem: function(entry) { + if (!entry.parent) { + return; + } + const grand = this.findParentContext(entry.parent); + const arr = entry.siblings; + const idx = arr.indexOf(entry.item); + if (idx !== -1) { + arr.splice(idx, 1); + const parentIdx = grand.list.indexOf(entry.parent); + grand.list.splice(parentIdx + 1, 0, entry.item); + this.syncItemsToDocument(); + } + }, + + findParentContext: function(target) { + let found = { list: this.items }; + const walk = (list) => { + list.forEach((item) => { + if (item === target) { + return; + } + if (item._children && item._children.indexOf(target) !== -1) { + found = { list: item._children, parent: item }; + } + if (item._children) { + walk(item._children); + } + }); + }; + walk(this.items); + return found; + }, + + // --- Drag and drop reordering ------------------------------------- + + onDragStart: function(entry, ev) { + this.dragItemId = entry.item._id; + if (ev.dataTransfer) { + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData('text/plain', String(entry.item._id)); + } + }, + + onDragOver: function(entry, ev) { + if (this.dragItemId === null || this.dragItemId === entry.item._id) { + return; + } + ev.preventDefault(); + this.dropTargetId = entry.item._id; + }, + + onDrop: function(entry) { + const draggedId = this.dragItemId; + this.dropTargetId = null; + this.dragItemId = null; + if (draggedId === null || draggedId === entry.item._id) { + return; + } + + const dragged = this.findEntryById(draggedId); + if (!dragged) { + return; + } + + // Do not drop a node into its own descendant. + if (this.isDescendant(dragged.item, entry.item)) { + return; + } + + // Remove from old position. + const fromArr = dragged.siblings; + fromArr.splice(fromArr.indexOf(dragged.item), 1); + + // Insert before the drop target within its sibling list. + const toArr = entry.siblings; + const targetIdx = toArr.indexOf(entry.item); + toArr.splice(targetIdx, 0, dragged.item); + + this.syncItemsToDocument(); + }, + + onDragEnd: function() { + this.dragItemId = null; + this.dropTargetId = null; + }, + + findEntryById: function(id) { + return this.flatItems.find((e) => e.item._id === id) || null; + }, + + isDescendant: function(ancestor, node) { + let found = false; + const walk = (list) => { + list.forEach((child) => { + if (child === node) { + found = true; + } + if (child._children) { + walk(child._children); + } + }); + }; + walk(ancestor._children || []); + return found; + }, + + // --- Edit Menu Item modal ----------------------------------------- + + editItem: function(item) { + if (this.selectedItem) { + this.selectedItem._selected = false; + } + this.selectedItem = item; + item._selected = true; + this.modalVisible = true; + + this.$nextTick(() => { + this.loadItemForm(item); + }); + }, + + closeModal: function() { + this.modalVisible = false; + }, + + applyAndClose: function() { + this.applyItemForm(); + this.modalVisible = false; + }, + + loadItemForm: function(item) { + const container = this.$refs.menuItemForm; + if (!container) { + return; + } + + oc.request(container, 'onLoadMenuItemForm', { + data: { bindMenuItemForm: 1 } + }).then(() => { + this.itemFormLoaded = true; + this.populateItemForm(item); + this.bindTypeChange(); + this.refreshReferenceOptions(item.reference, item.cmsPage); + }); + }, + + // Wire the Type dropdown so switching type reloads the reference/cmsPage options, + // matching the original plugin's cascade. + bindTypeChange: function() { + const form = this.$refs.menuItemForm; + if (!form) { + return; + } + const typeInput = form.querySelector('[name="menuItem[type]"]'); + if (typeInput && !typeInput._pagesTypeBound) { + typeInput._pagesTypeBound = true; + typeInput.addEventListener('change', () => { + this.refreshReferenceOptions('', ''); + }); + } + }, + + // Fetch type info for the current type and (re)populate the reference + cmsPage + // dropdowns, selecting the given values when present. + refreshReferenceOptions: function(selectedReference, selectedCmsPage) { + const form = this.$refs.menuItemForm; + if (!form) { + return; + } + const typeInput = form.querySelector('[name="menuItem[type]"]'); + const type = typeInput ? typeInput.value : ''; + if (!type) { + return; + } + + oc.request(form, 'onGetMenuItemTypeInfo', { + data: { type: type } + }).then((data) => { + const info = (data && data.menuItemTypeInfo) || {}; + this.fillSelect( + form.querySelector('[name="menuItem[reference]"]'), + this.flattenReferences(info.references || {}), + selectedReference + ); + this.fillSelect( + form.querySelector('[name="menuItem[cmsPage]"]'), + info.cmsPages || {}, + selectedCmsPage + ); + }); + }, + + // Flatten the (possibly nested) references structure into a flat {key: label} map. + flattenReferences: function(references) { + const flat = {}; + const walk = (map, prefix) => { + Object.keys(map).forEach((key) => { + const entry = map[key]; + const title = (entry && typeof entry === 'object') ? entry.title : entry; + flat[key] = (prefix ? prefix + ' / ' : '') + (title || key); + if (entry && typeof entry === 'object' && entry.items) { + walk(entry.items, flat[key]); + } + }); + }; + walk(references || {}, ''); + return flat; + }, + + fillSelect: function(select, options, selectedValue) { + if (!select) { + return; + } + select.innerHTML = ''; + Object.keys(options).forEach((value) => { + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = options[value]; + if (value === selectedValue) { + opt.selected = true; + } + select.appendChild(opt); + }); + select.dispatchEvent(new Event('change', { bubbles: true })); + }, + + populateItemForm: function(item) { + const form = this.$refs.menuItemForm; + if (!form) { + return; + } + + Object.keys(item).forEach((key) => { + if (key.charAt(0) === '_' || key === 'typeLabel') { + return; + } + const input = form.querySelector('[name="menuItem[' + key + ']"]'); + if (input) { + if (input.type === 'checkbox') { + input.checked = !!item[key] && item[key] !== '0'; + } + else { + input.value = item[key]; + } + input.dispatchEvent(new Event('change', { bubbles: true })); + } + }); + }, + + applyItemForm: function() { + const form = this.$refs.menuItemForm; + if (!form || !this.selectedItem) { + return; + } + + const formData = new FormData(form); + const viewBag = {}; + for (const [name, value] of formData.entries()) { + const vbMatch = name.match(/^menuItem\[viewBag\]\[([^\]]+)\]$/); + if (vbMatch) { + viewBag[vbMatch[1]] = value; + continue; + } + const m = name.match(/^menuItem\[([^\]]+)\]$/); + if (m) { + this.selectedItem[m[1]] = value; + } + } + this.selectedItem.viewBag = viewBag; + + // Refresh the row subtitle from the (possibly changed) type. + const typeInput = form.querySelector('[name="menuItem[type]"]'); + if (typeInput) { + const opt = typeInput.options ? typeInput.options[typeInput.selectedIndex] : null; + this.selectedItem.typeLabel = opt ? opt.text : this.selectedItem.type; + } + + this.syncItemsToDocument(); + }, + + documentCreatedOrLoaded: function() { + this.items = this.inflateItems(this.documentData.items || []); + }, + + onToolbarCommand: function(command, isHotkey) { + if (command === 'add-item') { + this.addItem(); + return; + } + this.handleBasicDocumentCommands(command, isHotkey); + } + } +}; diff --git a/vuecomponents/menueditor/partials/_menueditor.php b/vuecomponents/menueditor/partials/_menueditor.php new file mode 100644 index 00000000..8f4964ae --- /dev/null +++ b/vuecomponents/menueditor/partials/_menueditor.php @@ -0,0 +1,92 @@ + + + + + + + diff --git a/vuecomponents/staticpageeditor/assets/js/staticpageeditor.js b/vuecomponents/staticpageeditor/assets/js/staticpageeditor.js new file mode 100644 index 00000000..3818cd23 --- /dev/null +++ b/vuecomponents/staticpageeditor/assets/js/staticpageeditor.js @@ -0,0 +1,447 @@ +import { DocumentComponentBase } from '../../../../../../../modules/editor/assets/js/editor.extension.documentcomponent.base.js'; +import EditorModelDefinition from '../../../../../../../modules/backend/vuecomponents/monacoeditor/assets/js/modeldefinition.js'; + +export default { + extends: DocumentComponentBase, + data: function() { + return { + documentSettingsPopupTitle: this.trans('Static page') || 'Static Page', + // Monaco backs only the "code" surfaces (text-type placeholders). + codeEditorModelDefinitions: [], + codeModels: {}, + modelsReady: false, + // Tabs: the main content surface, one per layout placeholder, and one per + // layout syntax-field group. + activeSurfaceKey: 'markup', + loadedSyntaxGroups: {}, + // Each rich surface's richeditor connector owns its own toolbar-button array, + // keyed by surface key. The document toolbar renders the active surface's array + // (activeToolbarExtension) so switching tabs just swaps which array is shown - + // the connector never has to re-emit its buttons. + surfaceToolbars: {} + }; + }, + computed: { + // Every editable content region as a tab. + // - mode 'rich' = WYSIWYG richeditor (page content, html placeholders) + // - mode 'code' = Monaco (text-type placeholders) + // - mode 'syntax' = server-rendered Form-widget island (layout syntax fields) + contentSurfaces: function() { + const surfaces = [ + { key: 'markup', title: this.trans('Content') || 'Content', mode: 'rich', holder: 'root' } + ]; + + const info = (this.documentData && this.documentData.placeholderInfo) || {}; + Object.keys(info).forEach((code) => { + const meta = info[code] || {}; + surfaces.push({ + key: code, + title: meta.title || code, + mode: meta.type === 'text' ? 'code' : 'rich', + holder: 'placeholder' + }); + }); + + const groups = (this.documentData && this.documentData.syntaxFieldGroups) || []; + groups.forEach((group) => { + surfaces.push({ + key: group.key, + title: group.title, + mode: 'syntax', + tab: group.title, + containerId: 'pagesSyntax_' + group.key.replace(/[^a-z0-9]/gi, '') + }); + }); + + return surfaces; + }, + + hasTabs: function() { + return this.contentSurfaces.length > 1; + }, + + surfaceTabs: function() { + return this.contentSurfaces.map((surface) => ({ + key: surface.key, + label: surface.title + })); + }, + + syntaxSurfaces: function() { + return this.contentSurfaces.filter((s) => s.mode === 'syntax'); + }, + + activeSurface: function() { + return this.contentSurfaces.find((s) => s.key === this.activeSurfaceKey) + || this.contentSurfaces[0]; + }, + + showCodeEditor: function() { + return this.modelsReady + && this.activeSurface + && this.activeSurface.mode === 'code'; + }, + + // The richeditor toolbar buttons for the active surface (empty for non-rich). + activeToolbarExtension: function() { + const surface = this.activeSurface; + if (surface && surface.mode === 'rich') { + return this.surfaceToolbars[surface.key] || []; + } + return []; + }, + + toolbarElements: function() { + return [].concat([ + { + type: 'button', + icon: 'icon-save-cloud', + label: this.trans('backend::lang.form.save'), + hotkey: 'ctrl+s, cmd+s', + tooltip: this.trans('backend::lang.form.save'), + command: 'save' + }, + { + type: 'button', + icon: 'icon-settings', + label: this.trans('editor::lang.common.settings'), + command: 'settings', + hidden: !this.hasSettingsForm + }, + this.activeToolbarExtension, + { + type: 'separator' + }, + { + type: 'button', + icon: 'icon-delete', + disabled: this.isNewDocument, + command: 'delete', + hotkey: 'shift+option+d', + tooltip: this.trans('backend::lang.form.delete') + }, + { + type: 'button', + icon: this.documentHeaderCollapsed ? 'icon-angle-down' : 'icon-angle-up', + command: 'document:toggleToolbar', + fixedRight: true, + tooltip: this.trans('editor::lang.common.toggle_document_header') + } + ]); + } + }, + methods: { + getRootProperties: function() { + return ['fileName', 'markup', 'placeholders']; + }, + + getMainUiDocumentProperties: function() { + return ['title', 'url', 'markup', 'placeholders']; + }, + + surfaceValue: function(surface) { + if (surface.holder === 'root') { + return this.documentData.markup; + } + return this.documentData.placeholders[surface.key]; + }, + + setSurfaceValue: function(surface, value) { + if (surface.holder === 'root') { + this.documentData.markup = value; + } + else { + this.documentData.placeholders[surface.key] = value; + } + }, + + onSurfaceTabSelected: function(key) { + this.selectSurface(key); + }, + + selectSurface: function(key) { + this.activeSurfaceKey = key; + + this.$nextTick(() => { + const surface = this.activeSurface; + if (!surface) { + return; + } + + if (surface.mode === 'code' && this.$refs.editor) { + const def = this.codeModels[surface.key]; + if (def) { + this.$refs.editor.updateValue(def, this.surfaceValue(surface)); + this.$refs.editor.layout(); + } + } + else if (surface.mode === 'rich') { + this.refreshRichSurface(surface.key); + } + else if (surface.mode === 'syntax' && !this.loadedSyntaxGroups[surface.key]) { + this.loadSyntaxGroup(surface); + } + }); + }, + + // Refresh a rich surface's connector once it becomes active. The connector runs + // updateSize()/extendToolbar() on mount, but on first load Froala's wrapper may not + // be ready yet (so the resizer stays non-responsive until the tab is toggled). Retry + // until the Froala wrapper exists, then run both: + // - updateSize(): recomputes the resizable width/centering (responsive resize) + // - extendToolbar(): repopulates the toolbar buttons + refreshRichSurface: function(key, attempt) { + attempt = attempt || 0; + + if (this.activeSurfaceKey !== key || attempt > 20) { + return; + } + + const connector = this.$refs['richEditor_' + key]; + const inst = Array.isArray(connector) ? connector[0] : connector; + + // Wait for the connector and its Froala wrapper to exist before measuring. + const wrapperReady = inst && inst.$el && inst.$el.querySelector('.fr-wrapper'); + if (!wrapperReady) { + setTimeout(() => this.refreshRichSurface(key, attempt + 1), 50); + return; + } + + if (typeof inst.updateSize === 'function') { + inst.updateSize(); + } + if (typeof inst.extendToolbar === 'function') { + inst.extendToolbar(); + } + }, + + buildCodeModels: function() { + if (!this.documentData.placeholders || typeof this.documentData.placeholders !== 'object') { + this.documentData.placeholders = {}; + } + + const defs = []; + const models = {}; + + this.contentSurfaces.forEach((surface) => { + if (surface.mode !== 'code') { + return; + } + + if (surface.holder === 'placeholder' && this.documentData.placeholders[surface.key] === undefined) { + this.documentData.placeholders[surface.key] = ''; + } + + const holderObject = surface.holder === 'root' + ? this.documentData + : this.documentData.placeholders; + const holderProperty = surface.holder === 'root' ? 'markup' : surface.key; + + const def = new EditorModelDefinition( + 'plaintext', + surface.title, + holderObject, + holderProperty, + 'backend-icon-background monaco-document html' + ); + + defs.push(def); + models[surface.key] = def; + }); + + this.codeEditorModelDefinitions = defs; + this.codeModels = models; + }, + + ensurePlaceholderKeys: function() { + const info = (this.documentData && this.documentData.placeholderInfo) || {}; + if (!this.documentData.placeholders || typeof this.documentData.placeholders !== 'object') { + this.documentData.placeholders = {}; + } + Object.keys(info).forEach((code) => { + if (this.documentData.placeholders[code] === undefined) { + this.documentData.placeholders[code] = ''; + } + }); + }, + + getSaveDocumentData: function(inspectorDocumentData) { + const rootProperties = this.getRootProperties(); + const documentData = inspectorDocumentData ? inspectorDocumentData : this.documentData; + + const data = $.oc.vueUtils.getCleanObject(documentData); + const result = { settings: {} }; + + const ignoredProperties = ['placeholderInfo', 'syntaxFieldGroups']; + + Object.keys(data).forEach((property) => { + if (property === 'settings' || ignoredProperties.indexOf(property) !== -1) { + return; + } + + if (rootProperties.indexOf(property) !== -1) { + result[property] = data[property]; + } + else { + result.settings[property] = data[property]; + } + }); + + if (typeof data.settings === 'object' && data.settings !== null) { + Object.keys(data.settings).forEach((property) => { + if (rootProperties.indexOf(property) === -1 && result.settings[property] === undefined) { + result.settings[property] = data.settings[property]; + } + }); + } + + // Merge values from every loaded syntax-field island into settings (viewBag). + const syntaxData = this.collectSyntaxFieldData(); + Object.keys(syntaxData).forEach((key) => { + result.settings[key] = syntaxData[key]; + }); + + return result; + }, + + collectSyntaxFieldData: function() { + const result = {}; + + this.syntaxSurfaces.forEach((surface) => { + const form = this.$refs['form_' + surface.containerId]; + const el = Array.isArray(form) ? form[0] : form; + if (!this.loadedSyntaxGroups[surface.key] || !el) { + return; + } + + const formData = new FormData(el); + for (const [name, value] of formData.entries()) { + const match = name.match(/^syntaxFields\[viewBag\]\[([^\]]+)\](.*)$/); + if (!match) { + continue; + } + + const path = [match[1]]; + const bracketRe = /\[([^\]]*)\]/g; + let m; + while ((m = bracketRe.exec(match[2])) !== null) { + path.push(m[1]); + } + + this.assignNested(result, path, value); + } + }); + + return result; + }, + + assignNested: function(target, path, value) { + let node = target; + for (let i = 0; i < path.length - 1; i++) { + const key = path[i]; + if (node[key] === undefined || typeof node[key] !== 'object') { + node[key] = {}; + } + node = node[key]; + } + node[path[path.length - 1]] = value; + }, + + loadSyntaxGroup: function(surface) { + const form = this.$refs['form_' + surface.containerId]; + const el = Array.isArray(form) ? form[0] : form; + if (!el) { + return; + } + + // Load the group's Form-widget island. Core's MutationObserver auto-initializes + // the injected controls (repeater, mediafinder, richeditor, ...). + oc.request(el, 'onLoadSyntaxFields', { + data: { + path: this.documentMetadata.path, + tab: surface.tab, + containerId: surface.containerId + } + }).then(() => { + this.loadedSyntaxGroups[surface.key] = true; + }); + }, + + onToolbarCommand: function(command, isHotkey, ev) { + this.handleBasicDocumentCommands(command, isHotkey); + + const surface = this.activeSurface; + if (surface && surface.mode === 'rich') { + const connector = this.$refs['richEditor_' + this.activeSurfaceKey]; + const inst = Array.isArray(connector) ? connector[0] : connector; + if (inst && inst.internalEventBus) { + inst.internalEventBus.emit('toolbarcmd', { command: command, ev: ev }); + } + } + }, + + documentLoaded: function(data) { + this.$nextTick(() => { + if (this.$refs.editor) { + Object.keys(this.codeModels).forEach((key) => { + const def = this.codeModels[key]; + const surface = this.contentSurfaces.find((s) => s.key === key); + if (surface) { + this.$refs.editor.updateValue(def, this.surfaceValue(surface)); + } + }); + } + + // Ensure the initially-active rich surface's toolbar is populated. + if (this.activeSurface && this.activeSurface.mode === 'rich') { + this.refreshRichSurface(this.activeSurfaceKey); + } + }); + }, + + documentCreatedOrLoaded: function() { + this.ensurePlaceholderKeys(); + this.buildCodeModels(); + this.loadedSyntaxGroups = {}; + + // Pre-create a stable toolbar array for every rich surface before the editor + // panel renders, so each connector binds to (and mutates) its own live array. + const toolbars = {}; + this.contentSurfaces.forEach((surface) => { + if (surface.mode === 'rich') { + toolbars[surface.key] = []; + } + }); + this.surfaceToolbars = toolbars; + + this.activeSurfaceKey = 'markup'; + this.modelsReady = true; + }, + + // Keep the active rich surface's resizable width in sync on window resize. The + // connector's own resize handler is unreliable here (its listener loses the + // component `this`), so drive updateSize ourselves. Debounced. + onWindowResize: function() { + if (this.resizeDebounce) { + clearTimeout(this.resizeDebounce); + } + this.resizeDebounce = setTimeout(() => { + if (this.activeSurface && this.activeSurface.mode === 'rich') { + this.refreshRichSurface(this.activeSurfaceKey); + } + }, 10); + } + }, + mounted: function() { + this.boundWindowResize = this.onWindowResize.bind(this); + window.addEventListener('resize', this.boundWindowResize); + }, + beforeUnmount: function() { + if (this.boundWindowResize) { + window.removeEventListener('resize', this.boundWindowResize); + } + if (this.resizeDebounce) { + clearTimeout(this.resizeDebounce); + } + } +}; diff --git a/vuecomponents/staticpageeditor/partials/_staticpageeditor.php b/vuecomponents/staticpageeditor/partials/_staticpageeditor.php new file mode 100644 index 00000000..8d4c8919 --- /dev/null +++ b/vuecomponents/staticpageeditor/partials/_staticpageeditor.php @@ -0,0 +1,93 @@ + + + + + + + diff --git a/widgets/MenuList.php b/widgets/MenuList.php deleted file mode 100644 index a9a43d10..00000000 --- a/widgets/MenuList.php +++ /dev/null @@ -1,127 +0,0 @@ -alias = $alias; - $this->theme = Theme::getEditTheme(); - $this->dataIdPrefix = 'page-'.$this->theme->getDirName(); - - parent::__construct($controller, []); - $this->bindToController(); - } - - /** - * Renders the widget. - * @return string - */ - public function render() - { - return $this->makePartial('body', [ - 'data' => $this->getData() - ]); - } - - // - // Event handlers - // - - public function onUpdate() - { - $this->extendSelection(); - - return $this->updateList(); - } - - public function onSearch() - { - $this->setSearchTerm(Input::get('search')); - $this->extendSelection(); - - return $this->updateList(); - } - - // - // Methods for the internal use - // - - protected function getData() - { - $menus = Menu::listInTheme($this->theme, true); - - $searchTerm = Str::lower($this->getSearchTerm()); - - if (strlen($searchTerm)) { - $words = explode(' ', $searchTerm); - $filteredMenus = []; - - foreach ($menus as $menu) { - if ($this->textMatchesSearch($words, $menu->name.' '.$menu->fileName)) { - $filteredMenus[] = $menu; - } - } - - $menus = $filteredMenus; - } - - if ($sortMenusBy = Config::get('rainlab.pages::menus_sort_by', false)) { - return $menus->sortBy($sortMenusBy); - } - - return $menus; - } - - protected function updateList() - { - $vars = ['items' => $this->getData()]; - return ['#'.$this->getId('menu-list') => $this->makePartial('items', $vars)]; - } - - protected function getThemeSessionKey($prefix) - { - return $prefix . $this->theme->getDirName(); - } - - protected function getSession($key = null, $default = null) - { - $key = strlen($key) ? $this->getThemeSessionKey($key) : $key; - - return parent::getSession($key, $default); - } - - protected function putSession($key, $value) - { - return parent::putSession($this->getThemeSessionKey($key), $value); - } -} diff --git a/widgets/PageList.php b/widgets/PageList.php deleted file mode 100644 index 2ae2c415..00000000 --- a/widgets/PageList.php +++ /dev/null @@ -1,165 +0,0 @@ -alias = $alias; - $this->theme = Theme::getEditTheme(); - $this->dataIdPrefix = 'page-'.$this->theme->getDirName(); - - parent::__construct($controller, []); - $this->bindToController(); - } - - /** - * Renders the widget. - * @return string - */ - public function render() - { - return $this->makePartial('body', [ - 'data' => $this->getData() - ]); - } - - /* - * Event handlers - */ - - public function onReorder() - { - $structure = json_decode(Input::get('structure'), true); - if (!$structure) { - throw new SystemException('Invalid structure data posted.'); - } - - $pageList = new StaticPageList($this->theme); - $pageList->updateStructure($structure); - } - - public function onUpdate() - { - $this->extendSelection(); - - return $this->updateList(); - } - - public function onSearch() - { - $this->setSearchTerm(Input::get('search')); - $this->extendSelection(); - - return $this->updateList(); - } - - /* - * Methods for internal use - */ - - protected function getData() - { - $pageList = new StaticPageList($this->theme); - $pages = $pageList->getPageTree(true); - - $searchTerm = Str::lower($this->getSearchTerm()); - - if (strlen($searchTerm)) { - $words = explode(' ', $searchTerm); - - $iterator = function($pages) use (&$iterator, $words) { - $result = []; - - foreach ($pages as $page) { - if ($this->textMatchesSearch($words, $this->subtreeToText($page))) { - $result[] = (object) [ - 'page' => $page->page, - 'subpages' => $iterator($page->subpages) - ]; - } - } - - return $result; - }; - - $pages = $iterator($pages); - } - - return $pages; - } - - protected function getThemeSessionKey($prefix) - { - return $prefix.$this->theme->getDirName(); - } - - protected function updateList() - { - return ['#'.$this->getId('page-list') => $this->makePartial('items', ['items' => $this->getData()])]; - } - - protected function subtreeToText($page) - { - $result = $this->pageToText($page->page); - - $iterator = function($pages) use (&$iterator, &$result) { - foreach ($pages as $page) { - $result .= ' '.$this->pageToText($page->page); - $iterator($page->subpages); - } - }; - - $iterator($page->subpages); - - return $result; - } - - protected function pageToText($page) - { - $viewBag = $page->getViewBag(); - - return $page->getViewBag()->property('title').' '.$page->getViewBag()->property('url'); - } - - protected function getSession($key = null, $default = null) - { - $key = strlen($key) ? $this->getThemeSessionKey($key) : $key; - - return parent::getSession($key, $default); - } - - protected function putSession($key, $value) - { - return parent::putSession($this->getThemeSessionKey($key), $value); - } -} diff --git a/widgets/TemplateList.php b/widgets/TemplateList.php deleted file mode 100644 index 3e53ca39..00000000 --- a/widgets/TemplateList.php +++ /dev/null @@ -1,415 +0,0 @@ -'URL'] - */ - public $descriptionProperties = []; - - /** - * @var string object property to use as a description. - */ - public $descriptionProperty; - - /** - * @var string Message to display when there are no records in the list. - */ - public $noRecordsMessage = 'rainlab.pages::lang.template.no_list_records'; - - /** - * @var string Message to display when the Delete button is clicked. - */ - public $deleteConfirmation = 'rainlab.pages::lang.template.delete_confirm'; - - /** - * @var string Specifies the item type. - */ - public $itemType; - - /** - * @var string Extra CSS class name to apply to the control. - */ - public $controlClass; - - /** - * @var array A list of file name patterns to suppress / hide. - */ - public $ignoreDirectories = []; - - /** - * @var array Defines sorting properties. - * The sorting feature is disabled if there are no sorting properties defined. - */ - public $sortingProperties = []; - - /* - * Public methods - */ - - public function __construct($controller, $alias, callable $dataSource) - { - $this->alias = $alias; - $this->dataSource = $dataSource; - $this->theme = Theme::getEditTheme(); - $this->selectionInputName = 'template'; - $this->collapseSessionKey = $this->getThemeSessionKey('groups'); - - parent::__construct($controller, []); - - if (!Request::isXmlHttpRequest()) { - $this->resetSelection(); - } - - $configFile = 'config_' . snake_case($alias) .'.yaml'; - $config = $this->makeConfig($configFile); - - foreach ($config as $field => $value) { - if (property_exists($this, $field)) { - $this->$field = $value; - } - } - - $this->bindToController(); - } - - /** - * Renders the widget. - * @return string - */ - public function render() - { - $toolbarClass = Str::contains($this->controlClass, 'hero') ? 'separator' : null; - - $this->vars['toolbarClass'] = $toolbarClass; - - return $this->makePartial('body', [ - 'data' => $this->getData() - ]); - } - - /* - * Event handlers - */ - - public function onSearch() - { - $this->setSearchTerm(Input::get('search')); - $this->extendSelection(); - - return $this->updateList(); - } - - public function onUpdate() - { - $this->extendSelection(); - - return $this->updateList(); - } - - public function onApplySorting() - { - $this->setSortingProperty(Input::get('sortProperty')); - - $result = $this->updateList(); - $result['#'.$this->getId('sorting-options')] = $this->makePartial('sorting-options'); - - return $result; - } - - // - // Methods for the internal use - // - - protected function getData() - { - /* - * Load the data - */ - $items = call_user_func($this->dataSource); - - if ($items instanceof \October\Rain\Support\Collection) { - $items = $items->all(); - } - - $items = $this->removeIgnoredDirectories($items); - - $items = array_map([$this, 'normalizeItem'], $items); - - $this->sortItems($items); - - /* - * Apply the search - */ - $filteredItems = []; - $searchTerm = Str::lower($this->getSearchTerm()); - - if (strlen($searchTerm)) { - /* - * Exact - */ - foreach ($items as $index => $item) { - if ($this->itemContainsWord($searchTerm, $item, true)) { - $filteredItems[] = $item; - unset($items[$index]); - } - } - - /* - * Fuzzy - */ - $words = explode(' ', $searchTerm); - foreach ($items as $item) { - if ($this->itemMatchesSearch($words, $item)) { - $filteredItems[] = $item; - } - } - } - else { - $filteredItems = $items; - } - - /* - * Group the items - */ - $result = []; - $foundGroups = []; - foreach ($filteredItems as $itemData) { - $pos = strpos($itemData->fileName, '/'); - - if ($pos !== false) { - $group = substr($itemData->fileName, 0, $pos); - if (!array_key_exists($group, $foundGroups)) { - $newGroup = (object)[ - 'title' => $group, - 'items' => [] - ]; - - $foundGroups[$group] = $newGroup; - } - - $foundGroups[$group]->items[] = $itemData; - } - else { - $result[] = $itemData; - } - } - - // Sort folders by name regardless of the - // selected sorting options. - ksort($foundGroups); - - foreach ($foundGroups as $group) { - $result[] = $group; - } - - return $result; - } - - protected function sortItems(&$items) - { - $sortingProperty = $this->getSortingProperty(); - - usort($items, function ($a, $b) use ($sortingProperty) { - return strcmp($a->$sortingProperty, $b->$sortingProperty); - }); - } - - protected function removeIgnoredDirectories($items) - { - if (!$this->ignoreDirectories) { - return $items; - } - - $ignoreCache = []; - - $items = array_filter($items, function ($item) use (&$ignoreCache) { - $fileName = $item->getBaseFileName(); - $dirName = dirname($fileName); - - if (isset($ignoreCache[$dirName])) { - return false; - } - - foreach ($this->ignoreDirectories as $ignoreDir) { - if (File::fileNameMatch($dirName, $ignoreDir)) { - $ignoreCache[$dirName] = true; - return false; - } - } - - return true; - }); - - return $items; - } - - protected function normalizeItem($item) - { - $description = null; - if ($descriptionProperty = $this->descriptionProperty) { - $description = $item->$descriptionProperty; - } - - $descriptions = []; - foreach ($this->descriptionProperties as $property => $title) { - if ($item->$property) { - $descriptions[$title] = $item->$property; - } - } - - $result = [ - 'title' => $this->getItemTitle($item), - 'fileName' => $item->getFileName(), - 'description' => $description, - 'descriptions' => $descriptions, - 'dragValue' => $this->getItemDragValue($item) - ]; - - foreach ($this->sortingProperties as $property => $name) { - $result[$property] = $item->$property; - } - - return (object) $result; - } - - protected function getItemDragValue($item) - { - if ($item instanceof \Cms\Classes\Partial) { - return "{% partial '".$item->getBaseFileName()."' %}"; - } - - if ($item instanceof \Cms\Classes\Content) { - return "{% content '".$item->getBaseFileName()."' %}"; - } - - if ($item instanceof \Cms\Classes\Page) { - return "{{ '".$item->getBaseFileName()."'|page }}"; - } - - return ''; - } - - protected function getItemTitle($item) - { - $titleProperty = $this->titleProperty; - - if ($titleProperty) { - return $item->$titleProperty ?: basename($item->getFileName()); - } - - return basename($item->getFileName()); - } - - protected function setSearchTerm($term) - { - $this->searchTerm = trim($term); - $this->putSession('search', $this->searchTerm); - } - - protected function getSearchTerm() - { - return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search'); - } - - protected function updateList() - { - return [ - '#'.$this->getId('template-list') => $this->makePartial('items', ['items' => $this->getData()]) - ]; - } - - protected function itemMatchesSearch($words, $item) - { - foreach ($words as $word) { - $word = trim($word); - if (!strlen($word)) { - continue; - } - - if (!$this->itemContainsWord($word, $item)) { - return false; - } - } - - return true; - } - - protected function itemContainsWord($word, $item, $exact = false) - { - $operator = $exact ? 'is' : 'contains'; - - if (strlen($item->title) && Str::$operator(Str::lower($item->title), $word)) { - return true; - } - - if (Str::$operator(Str::lower($item->fileName), $word)) { - return true; - } - - if (Str::$operator(Str::lower($item->description), $word) && strlen($item->description)) { - return true; - } - - foreach ($item->descriptions as $value) { - if (Str::$operator(Str::lower($value), $word) && strlen($value)) { - return true; - } - } - - return false; - } - - protected function getThemeSessionKey($prefix) - { - return $prefix.$this->theme->getDirName(); - } - - protected function getSortingProperty() - { - $property = $this->getSession($this->getThemeSessionKey('sorting_property'), self::SORTING_FILENAME); - - if (!array_key_exists($property, $this->sortingProperties)) { - return self::SORTING_FILENAME; - } - - return $property; - } - - protected function setSortingProperty($property) - { - $this->putSession($this->getThemeSessionKey('sorting_property'), $property); - } -} diff --git a/widgets/menulist/partials/_body.htm b/widgets/menulist/partials/_body.htm deleted file mode 100644 index da0e6963..00000000 --- a/widgets/menulist/partials/_body.htm +++ /dev/null @@ -1,10 +0,0 @@ - -makePartial('toolbar') ?> - -
    -
    -
    - makePartial('menus', ['data' => $data]) ?> -
    -
    -
    \ No newline at end of file diff --git a/widgets/menulist/partials/_items.htm b/widgets/menulist/partials/_items.htm deleted file mode 100644 index 94696c3f..00000000 --- a/widgets/menulist/partials/_items.htm +++ /dev/null @@ -1,39 +0,0 @@ - -
      - - getBaseFileName(); - $dataId = 'menu-'.$this->theme->getDirName().'-'.$fileName; - $cbId = 'cb'.md5($item->getBaseFileName()); - ?> -
    • - - name) ?> - - code) ?> - - - - - - -
      - isItemSelected($dataId) ? 'checked' : null ?> - data-request="getEventHandler('onSelect') ?>" - value="1" /> - -
      -
    • - -
    - -

    noRecordsMessage)) ?>

    - - - - - diff --git a/widgets/menulist/partials/_menus.htm b/widgets/menulist/partials/_menus.htm deleted file mode 100644 index c5f03acd..00000000 --- a/widgets/menulist/partials/_menus.htm +++ /dev/null @@ -1,10 +0,0 @@ -
    -
    - -
    -
    \ No newline at end of file diff --git a/widgets/menulist/partials/_toolbar.htm b/widgets/menulist/partials/_toolbar.htm deleted file mode 100644 index 9332ec55..00000000 --- a/widgets/menulist/partials/_toolbar.htm +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    -
    - - -
    -
    - - -
    - " - data-track-input - data-load-indicator - data-load-indicator-opaque - data-request-success="$('getId('delete-button') ?>').trigger('oc.triggerOn.update')" - data-request="getEventHandler('onSearch') ?>" - /> -
    - -
    -
    \ No newline at end of file diff --git a/widgets/pagelist/partials/_body.htm b/widgets/pagelist/partials/_body.htm deleted file mode 100644 index cf5e883f..00000000 --- a/widgets/pagelist/partials/_body.htm +++ /dev/null @@ -1,10 +0,0 @@ - -makePartial('toolbar') ?> - -
    -
    -
    - makePartial('pages', ['data' => $data]) ?> -
    -
    -
    \ No newline at end of file diff --git a/widgets/pagelist/partials/_items.htm b/widgets/pagelist/partials/_items.htm deleted file mode 100644 index 61b07919..00000000 --- a/widgets/pagelist/partials/_items.htm +++ /dev/null @@ -1,9 +0,0 @@ - -
      - makePartial('treebranch', ['items' => $items]) ?> -
    - -

    noRecordsMessage) ?>

    - - - \ No newline at end of file diff --git a/widgets/pagelist/partials/_pages.htm b/widgets/pagelist/partials/_pages.htm deleted file mode 100644 index 4748886c..00000000 --- a/widgets/pagelist/partials/_pages.htm +++ /dev/null @@ -1,12 +0,0 @@ -
    -
    -
    - makePartial('items', ['items' => $data]) ?> -
    -
    -
    \ No newline at end of file diff --git a/widgets/pagelist/partials/_toolbar.htm b/widgets/pagelist/partials/_toolbar.htm deleted file mode 100644 index 460f516e..00000000 --- a/widgets/pagelist/partials/_toolbar.htm +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    -
    - - -
    -
    - - -
    - " - data-track-input - data-load-indicator - data-load-indicator-opaque - data-request-success="$('getId('delete-button') ?>').trigger('oc.triggerOn.update')" - data-request="getEventHandler('onSearch') ?>" - /> -
    - -
    -
    \ No newline at end of file diff --git a/widgets/pagelist/partials/_treebranch.htm b/widgets/pagelist/partials/_treebranch.htm deleted file mode 100644 index f8f09930..00000000 --- a/widgets/pagelist/partials/_treebranch.htm +++ /dev/null @@ -1,69 +0,0 @@ - - page->getBaseFileName(); - $groupStatus = $this->getCollapseStatus($fileName, false); - $dataId = $this->dataIdPrefix.'-'.$fileName; - $searchMode = strlen($this->getSearchTerm()) > 0; - $cbId = 'cb'.md5($fileName); - - $pageTitle = $pageObj->page->getViewBag()->property('title'); - $pageUrl = $pageObj->page->getViewBag()->property('url'); - - if (class_exists('\RainLab\Translate\Behaviors\TranslatableModel')) { - $locale = \RainLab\Translate\Classes\Translator::instance()->getLocale(); - $pageTitle = isset($pageObj->page->viewBag['title']) ? $pageObj->page->viewBag['title'] : $pageTitle; - $pageUrl = isset($pageObj->page->getViewBag()->property('localeUrl')[$locale]) ? $pageObj->page->getViewBag()->property('localeUrl')[$locale] : $pageUrl; - } - - ?> -
  • data-no-drag-mode - data-id="" - > -
    - Expand - - - - - - -
    - isItemSelected($fileName) ? 'checked' : null ?> - data-request="getEventHandler('onSelect') ?>" - value="1"> - -
    - - - - title="Dragging is disabled when the Search is active">Drag - -
    - -
      - subpages): ?> - makePartial('treebranch', ['items' => $subpages]) ?> - -
    -
  • - diff --git a/widgets/templatelist/partials/_body.htm b/widgets/templatelist/partials/_body.htm deleted file mode 100644 index a4273cb7..00000000 --- a/widgets/templatelist/partials/_body.htm +++ /dev/null @@ -1,8 +0,0 @@ -makePartial('toolbar') ?> -
    -
    -
    - makePartial('templates', ['data' => $data]) ?> -
    -
    -
    diff --git a/widgets/templatelist/partials/_items.htm b/widgets/templatelist/partials/_items.htm deleted file mode 100644 index d330ca32..00000000 --- a/widgets/templatelist/partials/_items.htm +++ /dev/null @@ -1,59 +0,0 @@ - - - -

    noRecordsMessage)) ?>

    - - - - - diff --git a/widgets/templatelist/partials/_sorting-options.htm b/widgets/templatelist/partials/_sorting-options.htm deleted file mode 100644 index 4aacc073..00000000 --- a/widgets/templatelist/partials/_sorting-options.htm +++ /dev/null @@ -1,7 +0,0 @@ -sortingProperties as $propertyName=>$propertyTitle): ?> -
  • getSortingProperty() == $propertyName): ?>class="active"> - - - -
  • - diff --git a/widgets/templatelist/partials/_templates.htm b/widgets/templatelist/partials/_templates.htm deleted file mode 100644 index e1f0b860..00000000 --- a/widgets/templatelist/partials/_templates.htm +++ /dev/null @@ -1,11 +0,0 @@ -
    -
    -
    - makePartial('items', ['items' => $data]) ?> -
    -
    -
    diff --git a/widgets/templatelist/partials/_toolbar.htm b/widgets/templatelist/partials/_toolbar.htm deleted file mode 100644 index c3ed60f8..00000000 --- a/widgets/templatelist/partials/_toolbar.htm +++ /dev/null @@ -1,51 +0,0 @@ -
    -
    - - -
    -
    - - - sortingProperties): ?> - - - - -
    -
    - - -
    - -
    - -
    -
    From 241205ad34cf2eaaedaac44d5d02d985d638b281 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 3 Sep 2026 21:00:11 +1000 Subject: [PATCH 02/11] Rewrite to use Editor module --- behaviors/EditorState.php | 22 ---------------------- classes/EditorExtension.php | 6 +++--- controllers/Index.php | 22 ++++++++++++++-------- 3 files changed, 17 insertions(+), 33 deletions(-) delete mode 100644 behaviors/EditorState.php diff --git a/behaviors/EditorState.php b/behaviors/EditorState.php deleted file mode 100644 index 2c455755..00000000 --- a/behaviors/EditorState.php +++ /dev/null @@ -1,22 +0,0 @@ -registerVueComponent(\Editor\VueComponents\EditorConflictResolver::class); $this->registerVueComponent(\Editor\VueComponents\Application::class); - // Register only the assets of extensions in the Pages context, keeping the page - // scoped to it. + // The StateManager behavior has already scoped the manager to the Pages context + // (via $editorContext), so these list only Pages-context extension assets. $manager = ExtensionManager::instance(); - foreach ($manager->listJsFiles(EditorExtension::CONTEXT) as $jsFile) { + foreach ($manager->listJsFiles() as $jsFile) { $this->addJs($jsFile, ['type' => 'module']); } - foreach ($manager->listVueComponents(EditorExtension::CONTEXT) as $componentClass) { + foreach ($manager->listVueComponents() as $componentClass) { $this->registerVueComponent($componentClass); } @@ -102,7 +108,7 @@ public function index_onCommand() // Only run commands for extensions belonging to the Pages context, keeping this // page isolated from the global editor extensions. $extension = ExtensionManager::instance()->getExtensionByNamespace($namespace); - if (!$extension->hasEditorContext(EditorExtension::CONTEXT)) { + if ($extension->getEditorContext() !== EditorExtension::CONTEXT) { throw new SystemException('Unsupported extension: '.$namespace); } @@ -130,7 +136,7 @@ public function onListExtensionNavigatorSections() } $extension = ExtensionManager::instance()->getExtensionByNamespace($namespace); - if (!$extension->hasEditorContext(EditorExtension::CONTEXT)) { + if ($extension->getEditorContext() !== EditorExtension::CONTEXT) { throw new SystemException('Unsupported extension namespace'); } From 7117b5b9025d56567d68c8ae606fd38dbdfcfac6 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 3 Sep 2026 21:35:29 +1000 Subject: [PATCH 03/11] Progress on menu editor --- assets/css/editor.css | 77 ++++++++++++++++++- classes/editorextension/HasMenuCrud.php | 1 + .../menueditor/assets/js/menueditor.js | 67 ++++++++++++---- .../menueditor/partials/_menueditor.php | 17 ++-- 4 files changed, 141 insertions(+), 21 deletions(-) diff --git a/assets/css/editor.css b/assets/css/editor.css index 92155614..6a2adc1e 100644 --- a/assets/css/editor.css +++ b/assets/css/editor.css @@ -3,6 +3,7 @@ /* Menu editor: tree list + item form panel. */ .pages-menu-editor .menu-item-row { + position: relative; display: flex; align-items: center; gap: 8px; @@ -10,6 +11,7 @@ border-radius: 4px; cursor: pointer; user-select: none; + border: 1px solid transparent; } .pages-menu-editor .menu-item-row:hover { @@ -62,6 +64,77 @@ flex: 0 0 auto; } -.pages-menu-editor .menu-item-row.drop-target { - box-shadow: inset 0 2px 0 var(--oc-primary-color, #6a70f2); +/* Flat, subtle icon actions matching the editor toolbar (transparent, muted icon, + light hover), revealed on row hover to keep the list clean at rest. */ +.pages-menu-editor .menu-item-actions { + display: flex; + gap: 2px; + flex: 0 0 auto; + opacity: 0; + transition: opacity .12s; +} + +.pages-menu-editor .menu-item-row:hover .menu-item-actions, +.pages-menu-editor .menu-item-row.selected .menu-item-actions { + opacity: 1; +} + +.pages-menu-editor .menu-item-action { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + padding: 0; + border: none; + border-radius: 4px; + background: transparent; + color: var(--oc-text-muted, #72809d); + font-size: 13px; + cursor: pointer; + box-shadow: none; +} + +.pages-menu-editor .menu-item-action:hover { + background: var(--oc-list-hover-bg, rgba(0, 0, 0, .06)); + color: var(--oc-text-color, #33414f); +} + +.pages-menu-editor .menu-item-action-danger:hover { + color: var(--oc-danger-color, #cc3300); +} + +/* Keep actions legible when the row is selected (dark background). */ +.pages-menu-editor .menu-item-row.selected .menu-item-action { + color: rgba(255, 255, 255, .8); +} + +.pages-menu-editor .menu-item-row.selected .menu-item-action:hover { + background: rgba(255, 255, 255, .18); + color: #fff; +} + +/* Drop cues (mirror the sidebar page tree). before/after = a drop line to reorder; + inside = a border around the row you'd nest into. */ +.pages-menu-editor .menu-item-row.drop-before::before, +.pages-menu-editor .menu-item-row.drop-after::after { + content: ''; + position: absolute; + left: 0; + right: 0; + height: 2px; + background: var(--oc-primary-color, #6a70f2); +} + +.pages-menu-editor .menu-item-row.drop-before::before { + top: -1px; +} + +.pages-menu-editor .menu-item-row.drop-after::after { + bottom: -1px; +} + +.pages-menu-editor .menu-item-row.drop-inside { + border-color: var(--oc-primary-color, #6a70f2); + background: var(--oc-primary-color-light, rgba(106, 112, 242, .08)); } diff --git a/classes/editorextension/HasMenuCrud.php b/classes/editorextension/HasMenuCrud.php index b61b3716..d044ddae 100644 --- a/classes/editorextension/HasMenuCrud.php +++ b/classes/editorextension/HasMenuCrud.php @@ -99,6 +99,7 @@ protected function deleteMenuDocument($controller) protected function menuToDocumentArray(Menu $menu): array { return [ + 'name' => $menu->name, 'code' => $menu->getBaseFileName(), 'items' => $this->itemsToArray($menu->items), 'settings' => [ diff --git a/vuecomponents/menueditor/assets/js/menueditor.js b/vuecomponents/menueditor/assets/js/menueditor.js index 6433e4e8..b2d96bb1 100644 --- a/vuecomponents/menueditor/assets/js/menueditor.js +++ b/vuecomponents/menueditor/assets/js/menueditor.js @@ -5,6 +5,7 @@ export default { data: function() { return { documentSettingsPopupTitle: this.trans('Menu') || 'Menu', + documentTitleProperty: 'name', items: [], selectedItem: null, itemFormLoaded: false, @@ -12,7 +13,8 @@ export default { newItemTitle: this.trans('New menu item'), nextItemId: 1, dragItemId: null, - dropTargetId: null + dropTargetId: null, + dropMode: null }; }, computed: { @@ -256,7 +258,9 @@ export default { return found; }, - // --- Drag and drop reordering ------------------------------------- + // --- Drag and drop reordering + nesting --------------------------- + // Mirrors the sidebar page tree: dropping on the top/bottom edge of a row reorders + // it before/after that row; dropping on the middle nests it inside that row. onDragStart: function(entry, ev) { this.dragItemId = entry.item._id; @@ -270,36 +274,72 @@ export default { if (this.dragItemId === null || this.dragItemId === entry.item._id) { return; } + + // Cannot drop a node into its own descendant. + const dragged = this.findEntryById(this.dragItemId); + if (dragged && this.isDescendant(dragged.item, entry.item)) { + return; + } + ev.preventDefault(); + + // Which third of the row is the cursor over? top = before, middle = inside, + // bottom = after. + const rect = ev.currentTarget.getBoundingClientRect(); + const offset = ev.clientY - rect.top; + const third = rect.height / 3; + + let mode; + if (offset < third) { + mode = 'before'; + } + else if (offset > third * 2) { + mode = 'after'; + } + else { + mode = 'inside'; + } + this.dropTargetId = entry.item._id; + this.dropMode = mode; }, onDrop: function(entry) { const draggedId = this.dragItemId; + const mode = this.dropMode; this.dropTargetId = null; + this.dropMode = null; this.dragItemId = null; + if (draggedId === null || draggedId === entry.item._id) { return; } const dragged = this.findEntryById(draggedId); - if (!dragged) { - return; - } - - // Do not drop a node into its own descendant. - if (this.isDescendant(dragged.item, entry.item)) { + if (!dragged || this.isDescendant(dragged.item, entry.item)) { return; } - // Remove from old position. + // Detach the dragged item from its current parent list. const fromArr = dragged.siblings; fromArr.splice(fromArr.indexOf(dragged.item), 1); - // Insert before the drop target within its sibling list. - const toArr = entry.siblings; - const targetIdx = toArr.indexOf(entry.item); - toArr.splice(targetIdx, 0, dragged.item); + if (mode === 'inside') { + // Nest as the first child of the target. + if (!entry.item._children) { + entry.item._children = []; + } + entry.item._children.unshift(dragged.item); + } + else { + // Reorder within the target's sibling list, before or after it. + const toArr = entry.siblings; + let targetIdx = toArr.indexOf(entry.item); + if (mode === 'after') { + targetIdx += 1; + } + toArr.splice(targetIdx, 0, dragged.item); + } this.syncItemsToDocument(); }, @@ -307,6 +347,7 @@ export default { onDragEnd: function() { this.dragItemId = null; this.dropTargetId = null; + this.dropMode = null; }, findEntryById: function(id) { diff --git a/vuecomponents/menueditor/partials/_menueditor.php b/vuecomponents/menueditor/partials/_menueditor.php index 8f4964ae..21660f2f 100644 --- a/vuecomponents/menueditor/partials/_menueditor.php +++ b/vuecomponents/menueditor/partials/_menueditor.php @@ -31,7 +31,12 @@ v-for="entry in flatItems" :key="entry.item._id" class="menu-item-row" - :class="{ selected: entry.item._selected, 'drop-target': dropTargetId === entry.item._id }" + :class="{ + selected: entry.item._selected, + 'drop-before': dropTargetId === entry.item._id && dropMode === 'before', + 'drop-after': dropTargetId === entry.item._id && dropMode === 'after', + 'drop-inside': dropTargetId === entry.item._id && dropMode === 'inside' + }" :style="{ marginLeft: (entry.depth * 24) + 'px' }" draggable="true" @dragstart="onDragStart(entry, $event)" @@ -47,11 +52,11 @@ class="menu-item-row" {{ itemSubtitle(entry.item) }} - - - - - + + + + + From d897e7b6e7389de64cf3ba411a1a6675aad4e7b6 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 3 Sep 2026 21:37:59 +1000 Subject: [PATCH 04/11] Refactor CSS --- assets/css/editor.css | 277 +++++++++++++++++++++--------------------- 1 file changed, 141 insertions(+), 136 deletions(-) diff --git a/assets/css/editor.css b/assets/css/editor.css index 6a2adc1e..e554b4cc 100644 --- a/assets/css/editor.css +++ b/assets/css/editor.css @@ -1,140 +1,145 @@ /* Content-region tabs use the shared backend-tabs component with the "primary" style (modules/backend/vuecomponents/tabs). No plugin CSS needed. */ -/* Menu editor: tree list + item form panel. */ -.pages-menu-editor .menu-item-row { - position: relative; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 8px; - border-radius: 4px; - cursor: pointer; - user-select: none; - border: 1px solid transparent; -} - -.pages-menu-editor .menu-item-row:hover { - background: var(--oc-list-hover-bg, rgba(0, 0, 0, .03)); -} - -.pages-menu-editor .menu-item-row.selected { - background: var(--oc-primary-color, #6a70f2); - color: #fff; -} - -.pages-menu-editor .menu-item-row.selected .menu-item-subtitle { - color: rgba(255, 255, 255, .8); -} - -.pages-menu-editor .menu-item-icon { - flex: 0 0 auto; - width: 18px; - text-align: center; - opacity: .7; -} - -.pages-menu-editor .menu-item-label { - flex: 1 1 auto; - min-width: 0; -} - -.pages-menu-editor .menu-item-title { - display: block; - font-size: 13px; - line-height: 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.pages-menu-editor .menu-item-subtitle { - display: block; - font-size: 11px; - line-height: 14px; - color: var(--oc-text-muted-color, #97a1ab); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.pages-menu-editor .menu-item-drag { - cursor: grab; - opacity: .35; - flex: 0 0 auto; -} - -/* Flat, subtle icon actions matching the editor toolbar (transparent, muted icon, - light hover), revealed on row hover to keep the list clean at rest. */ -.pages-menu-editor .menu-item-actions { - display: flex; - gap: 2px; - flex: 0 0 auto; - opacity: 0; - transition: opacity .12s; -} - -.pages-menu-editor .menu-item-row:hover .menu-item-actions, -.pages-menu-editor .menu-item-row.selected .menu-item-actions { - opacity: 1; -} - -.pages-menu-editor .menu-item-action { - display: inline-flex; - align-items: center; - justify-content: center; - width: 26px; - height: 26px; - padding: 0; - border: none; - border-radius: 4px; - background: transparent; - color: var(--oc-text-muted, #72809d); - font-size: 13px; - cursor: pointer; - box-shadow: none; -} - -.pages-menu-editor .menu-item-action:hover { - background: var(--oc-list-hover-bg, rgba(0, 0, 0, .06)); - color: var(--oc-text-color, #33414f); -} - -.pages-menu-editor .menu-item-action-danger:hover { - color: var(--oc-danger-color, #cc3300); -} - -/* Keep actions legible when the row is selected (dark background). */ -.pages-menu-editor .menu-item-row.selected .menu-item-action { - color: rgba(255, 255, 255, .8); -} - -.pages-menu-editor .menu-item-row.selected .menu-item-action:hover { - background: rgba(255, 255, 255, .18); - color: #fff; -} - -/* Drop cues (mirror the sidebar page tree). before/after = a drop line to reorder; - inside = a border around the row you'd nest into. */ -.pages-menu-editor .menu-item-row.drop-before::before, -.pages-menu-editor .menu-item-row.drop-after::after { - content: ''; - position: absolute; - left: 0; - right: 0; - height: 2px; - background: var(--oc-primary-color, #6a70f2); -} - -.pages-menu-editor .menu-item-row.drop-before::before { - top: -1px; -} - -.pages-menu-editor .menu-item-row.drop-after::after { - bottom: -1px; -} - -.pages-menu-editor .menu-item-row.drop-inside { - border-color: var(--oc-primary-color, #6a70f2); - background: var(--oc-primary-color-light, rgba(106, 112, 242, .08)); +/* Menu editor: item list + item form panel. */ +.pages-menu-editor { + .menu-item-row { + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border: 1px solid transparent; + border-radius: 4px; + cursor: pointer; + user-select: none; + + &:hover { + background: var(--oc-list-hover-bg, rgba(0, 0, 0, .03)); + } + + &.selected { + background: var(--oc-primary-color, #6a70f2); + color: #fff; + + .menu-item-subtitle { + color: rgba(255, 255, 255, .8); + } + + .menu-item-actions { + opacity: 1; + } + + /* Keep actions legible on the dark selected background. */ + .menu-item-action { + color: rgba(255, 255, 255, .8); + + &:hover { + background: rgba(255, 255, 255, .18); + color: #fff; + } + } + } + + &:hover .menu-item-actions { + opacity: 1; + } + + /* Drop cues (mirror the sidebar page tree). before/after = a drop line to + reorder; inside = a border around the row you'd nest into. */ + &.drop-before::before, + &.drop-after::after { + content: ''; + position: absolute; + left: 0; + right: 0; + height: 2px; + background: var(--oc-primary-color, #6a70f2); + } + + &.drop-before::before { + top: -1px; + } + + &.drop-after::after { + bottom: -1px; + } + + &.drop-inside { + border-color: var(--oc-primary-color, #6a70f2); + background: var(--oc-primary-color-light, rgba(106, 112, 242, .08)); + } + } + + .menu-item-icon { + flex: 0 0 auto; + width: 18px; + text-align: center; + opacity: .7; + } + + .menu-item-drag { + flex: 0 0 auto; + cursor: grab; + opacity: .35; + } + + .menu-item-label { + flex: 1 1 auto; + min-width: 0; + } + + .menu-item-title { + display: block; + font-size: 13px; + line-height: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .menu-item-subtitle { + display: block; + font-size: 11px; + line-height: 14px; + color: var(--oc-text-muted-color, #97a1ab); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + /* Flat, subtle icon actions matching the editor toolbar (transparent, muted icon, + light hover), revealed on row hover to keep the list clean at rest. */ + .menu-item-actions { + display: flex; + flex: 0 0 auto; + gap: 2px; + opacity: 0; + transition: opacity .12s; + } + + .menu-item-action { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + padding: 0; + border: none; + border-radius: 4px; + background: transparent; + color: var(--oc-text-muted, #72809d); + font-size: 13px; + cursor: pointer; + box-shadow: none; + + &:hover { + background: var(--oc-list-hover-bg, rgba(0, 0, 0, .06)); + color: var(--oc-text-color, #33414f); + } + + &.menu-item-action-danger:hover { + color: var(--oc-danger-color, #cc3300); + } + } } From 2827b48703c90461a915fb089dec945e055a601c Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Fri, 4 Sep 2026 17:32:48 +1000 Subject: [PATCH 05/11] Restore prior updates --- updates/version.yaml | 93 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/updates/version.yaml b/updates/version.yaml index 7b4bbc6b..bbf3c5d0 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -1 +1,92 @@ -v3.0.0: Modernized backend editor rebuilt on the Vue Editor module. File-based data model and frontend components unchanged for drop-in compatibility. +v1.0.1: Implemented the static pages management and the Static Page component. +v1.0.2: Fixed the page preview URL. +v1.0.3: Implemented menus. +v1.0.4: Implemented the content block management and placeholder support. +v1.0.5: Added support for the Sitemap plugin. +v1.0.6: Minor updates to the internal API. +v1.0.7: Added the Snippets feature. +v1.0.8: Minor improvements to the code. +v1.0.9: Fixes issue where Snippet tab is missing from the Partials form. +v1.0.10: Add translations for various locales. +v1.0.11: Fixes issue where placeholders tabs were missing from Page form. +v1.0.12: Implement Media Manager support. +v1.1.0: Adds meta title and description to pages. Adds |staticPage filter. +v1.1.1: Add support for Syntax Fields. +v1.1.2: Static Breadcrumbs component now respects the hide from navigation setting. +v1.1.3: Minor back-end styling fix. +v1.1.4: Minor fix to the StaticPage component API. +v1.1.5: Fixes bug when using syntax fields. +v1.1.6: Minor styling fix to the back-end UI. +v1.1.7: Improved menu item form to include CSS class, open in a new window and hidden flag. +v1.1.8: Improved the output of snippet partials when saved. +v1.1.9: Minor update to snippet inspector internal API. +v1.1.10: Fixes a bug where selecting a layout causes permanent unsaved changes. +v1.1.11: Add support for repeater syntax field. +v1.2.0: Added support for translations, UI updates. +v1.2.1: Use nice titles when listing the content files. +v1.2.2: Minor styling update. +v1.2.3: Snippets can now be moved by dragging them. +v1.2.4: Fixes a bug where the cursor is misplaced when editing text files. +v1.2.5: Fixes a bug where the parent page is lost upon changing a page layout. +v1.2.6: Shared view variables are now passed to static pages. +v1.2.7: Fixes issue with duplicating properties when adding multiple snippets on the same page. +v1.2.8: Fixes a bug where creating a content block without extension doesn't save the contents to file. +v1.2.9: Add conditional support for translating page URLs. +v1.2.10: Streamline generation of URLs to use the new Cms::url helper. +v1.2.11: Implements repeater usage with translate plugin. +v1.2.12: Fixes minor issue when using snippets and switching the application locale. +v1.2.13: Fixes bug when AJAX is used on a page that does not yet exist. +v1.2.14: Add theme logging support for changes made to menus. +v1.2.15: Back-end navigation sort order updated. +v1.2.16: Fixes a bug when saving a template that has been modified outside of the CMS (mtime mismatch). +v1.2.17: Changes locations of custom fields to secondary tabs instead of the primary Settings area. New menu search ability on adding menu items +v1.2.18: Fixes cache-invalidation issues when RainLab.Translate is not installed. Added Greek & Simplified Chinese translations. Removed deprecated calls. Allowed saving HTML in snippet properties. Added support for the MediaFinder in menu items. +v1.2.19: Catch exception with corrupted menu file. +v1.2.20: StaticMenu component now exposes menuName property; added pages.menu.referencesGenerated event. +v1.2.21: Fixes a bug where last Static Menu item cannot be deleted. Improved Persian, Slovak and Turkish translations. +v1.3.0: Added support for using Database-driven Themes when enabled in the CMS configuration. +v1.3.1: Added ChildPages Component, prevent hidden pages from being returned via menu item resolver. +v1.3.2: Fixes error when creating a subpage whose parent has no layout set. +v1.3.3: Improves user experience for users with only partial access through permissions +v1.3.4: Fix error where large menus were being truncated due to the PHP "max_input_vars" configuration value. Improved Slovenian translation. +v1.3.5: Minor fix to bust the browser cache for JS assets. Prevent duplicate property fields in snippet inspector. +v1.3.6: ChildPages component now displays localized page titles from Translate plugin. +v1.3.7: Adds MenuPicker formwidget. Adds future support for v2.0 of October CMS. +v1.4.0: Fixes bug when adding menu items in October CMS v2.0. +v1.4.1: Fixes support for configuration values. +v1.4.3: Fixes page deletion is newer platform builds. +v1.4.4: Disable touch device detection +v1.4.5: Minor styling improvements +v1.4.6: Minor styling improvements +v1.4.7: Minor layout fix in the Page editor +v1.4.8: Fixes rich editor usage inside repeaters. Adds getProcessedMarkup event. +v1.4.9: Fixes a lifecycle issue when switching the page layout. +v1.4.10: Fixes maintenance mode when using static pages. +v1.4.11: Adds type hidden to content placeholders. +v1.4.12: Improve support with October v2.2 +v1.5.0: Improve support with October v3.0 +v1.5.4: Compatibility updates +v1.5.5: Fixes media finder added to menu in October v2 +v1.5.6: Fixes concurrency save form in October v3 +v1.5.7: Adds page finder support for October v3.2 +v1.5.8: Fixes resolving links used in static pages +v1.5.9: Fixes fancy layout with nested forms +v1.5.10: Adds forward compatibility with October CMS v3.4 +v1.5.12: Fixes more areas of forward compatibility +v2.0.1: Compatibility with core snippets +v2.0.2: Fixes bug opening menu with tailor list menu items +v2.0.3: Fixes bug showing wrong placeholder code +v2.0.4: Fixes sidebar delete button in October v3.5 +v2.0.5: Compatibility fixes for October v3.6 +v2.1.0: Tabs are now stored and reopened on page load +v2.1.1: Fixes index page spamming event log +v2.1.2: Fixes clearing cache +v2.1.3: Compatibility fixes for October v3.7 +v2.2.0: Adds sort configuration to page list +v2.2.1: Compatibility fixes for October v4.2 +v2.2.2: Fixes translatable globe showing for fields +v2.2.3: Fixes placeholder tab missing from the Static Page editor on Twig 3.12+ +v2.2.4: Fixes tab rendering in Firefox +v2.2.5: Fixes performance of tree control UI +v2.2.6: Compatibility fixes for October v4.3 +v3.0.0: Modernized backend editor rebuilt on the Vue Editor module From 577f9ecb379bd0ca62c20c9e7465d3d78ea2811b Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Mon, 7 Sep 2026 16:54:09 +1000 Subject: [PATCH 06/11] Progress on rewrite --- DOCS.md | 36 ++-- Plugin.php | 23 +- assets/css/editor.css | 60 +++++- classes/Controller.php | 3 + classes/EditorExtension.php | 50 +++-- classes/Menu.php | 52 ++++- classes/MenuItem.php | 56 +---- classes/Page.php | 143 ++++++++++++- classes/PageLocale.php | 95 +++++++++ classes/Router.php | 25 ++- classes/editorextension/HasMenuCrud.php | 97 ++++++++- classes/editorextension/HasStaticPageCrud.php | 184 +++++++++++++++- classes/menu/Fields.php | 37 ---- classes/staticpage/Fields.php | 22 +- controllers/index/HasMenuItemForm.php | 28 ++- controllers/index/HasSyntaxFields.php | 18 +- lang/en.json | 6 +- .../menueditor/assets/js/menueditor.js | 199 ++++++++++++++++-- .../menueditor/partials/_menueditor.php | 46 ++-- .../assets/js/staticpageeditor.js | 45 +++- .../partials/_staticpageeditor.php | 21 +- 21 files changed, 1036 insertions(+), 210 deletions(-) create mode 100644 classes/PageLocale.php delete mode 100644 classes/menu/Fields.php diff --git a/DOCS.md b/DOCS.md index 85cfe3f6..eca57bec 100644 --- a/DOCS.md +++ b/DOCS.md @@ -201,31 +201,33 @@ To prevent a placeholder from appearing in the editor set the `type` attribute t ## Creating New Menu Item Types -Plugins can extend the Static Pages plugin with new menu item types. Please refer to the [Blog plugin](https://octobercms.com/plugin/rainlab-blog) for the integration example. New item types are registered with the API events triggered by the Static Pages plugin. The event handlers should be defined in the `boot()` method of the [plugin registration file](https://octobercms.com/docs/plugin/registration#registration-file). There are three events that should be handled in the plugin. +Plugins can extend the Static Pages plugin with new menu item types. Please refer to the [Blog plugin](https://octobercms.com/plugin/rainlab-blog) for the integration example. New item types are registered with the core page lookup API events, shared with the CMS `pagefinder` form widget — one registration makes a type available in both the menu editor and the page finder. The event handlers should be defined in the `boot()` method of the [plugin registration file](https://octobercms.com/docs/plugin/registration#registration-file). There are three events that should be handled in the plugin. -- `pages.menuitem.listType` event handler should return a list of new menu item types supported by the plugin. -- `pages.menuitem.getTypeInfo` event handler returns detailed information about a menu item type. -- `pages.menuitem.resolveItem` event handler "resolves" a menu item information and returns the actual item URL, title, an indicator whether the item is currently active, and subitems, if any. +- `cms.pageLookup.listTypes` event handler should return a list of new menu item types supported by the plugin. +- `cms.pageLookup.getTypeInfo` event handler returns detailed information about a menu item type. +- `cms.pageLookup.resolveItem` event handler "resolves" a menu item information and returns the actual item URL, title, an indicator whether the item is currently active, and subitems, if any. + +> **Note**: earlier versions of this plugin used `pages.menuitem.*` events for this purpose; these are no longer fired and plugins should migrate to the `cms.pageLookup.*` events above (same handler signatures). The next example shows an event handler registration code for the Blog plugin. The Blog plugin registers two item types. As you can see, the Blog plugin uses the Category class to handle the events. That's a recommended approach. ```php public function boot() { - Event::listen('pages.menuitem.listTypes', function() { + Event::listen('cms.pageLookup.listTypes', function() { return [ 'blog-category'=>'Blog category', 'all-blog-categories'=>'All blog categories', ]; }); - Event::listen('pages.menuitem.getTypeInfo', function($type) { + Event::listen('cms.pageLookup.getTypeInfo', function($type) { if ($type == 'blog-category' || $type == 'all-blog-categories') { return Category::getMenuTypeInfo($type); } }); - Event::listen('pages.menuitem.resolveItem', function($type, $item, $url, $theme) { + Event::listen('cms.pageLookup.resolveItem', function($type, $item, $url, $theme) { if ($type == 'blog-category' || $type == 'all-blog-categories') { return Category::resolveMenuItem($item, $url, $theme); } @@ -235,17 +237,25 @@ public function boot() ### Registering New Menu Item Types -New menu item types are registered with the `pages.menuitem.listTypes` event handlers. The handler should return an associative array with the type codes in indexes and type names in values. It's highly recommended to use the plugin name in the type codes, to avoid conflicts with other menu item type providers. Example: +New menu item types are registered with the `cms.pageLookup.listTypes` event handlers. The handler should return an associative array with the type codes in indexes and type names in values. It's highly recommended to use the plugin name in the type codes, to avoid conflicts with other menu item type providers. Example: + +```php +[ + 'my-plugin-item-type' => 'My plugin menu item type' +] +``` + +Types that generate nested items can use the extended label format so that single-URL contexts (such as the page finder in single mode) can exclude them: ```php [ - `my-plugin-item-type` => 'My plugin menu item type' + 'all-my-plugin-items' => ['label' => 'All my plugin items', 'nesting' => true] ] ``` ### Returning Information About an Item Type -Plugins should provide detailed information about the supported menu item types with the `pages.menuitem.getTypeInfo` event handlers. The handler gets a single parameter - the menu item type code (one of the codes you registered with the `pages.menuitem.listTypes` handler). The handler code must check whether the requested item type code belongs to the plugin. The handler should return an associative array in the following format: +Plugins should provide detailed information about the supported menu item types with the `cms.pageLookup.getTypeInfo` event handlers. The handler gets a single parameter - the menu item type code (one of the codes you registered with the `cms.pageLookup.listTypes` handler). The handler code must check whether the requested item type code belongs to the plugin. The handler should return an associative array in the following format: ``` Array ( @@ -287,7 +297,7 @@ The format for references with subitems is ['item-key' => ['title'=>'Item title', 'items'=>[...]]] ``` -The reference keys should reflect the object identifier they represent. For blog categories keys match the category identifiers. A plugin should be able to load an object by its key in the `pages.menuitem.resolveItem` event handler. The references element is optional, it is required only if a menu item type supports the Reference drop-down, or, in other words, if the user should be able to select an object the menu item refers to. +The reference keys should reflect the object identifier they represent. For blog categories keys match the category identifiers. A plugin should be able to load an object by its key in the `cms.pageLookup.resolveItem` event handler. The references element is optional, it is required only if a menu item type supports the Reference drop-down, or, in other words, if the user should be able to select an object the menu item refers to. #### cmsPages element @@ -320,10 +330,10 @@ return $result; ### Resolving Menu Items -When the Static Pages plugin generates a menu on the front-end, every menu item should **resolved** by the plugin that supplies the menu item type. The process of resolving involves generating the real item URL, determining whether the menu item is active, and generating the subitems (if required). Plugins should register the `pages.menuitem.resolveItem` event handler in order to resolve menu items. The event handler takes four arguments: +When the Static Pages plugin generates a menu on the front-end, every menu item should **resolved** by the plugin that supplies the menu item type. The process of resolving involves generating the real item URL, determining whether the menu item is active, and generating the subitems (if required). Plugins should register the `cms.pageLookup.resolveItem` event handler in order to resolve menu items. The event handler takes four arguments: * `$type` - the item type name. Plugins must only handle item types they provide and ignore other types. -* `$item` - the menu item object (RainLab\Pages\Classes\MenuItem). The menu item object represents the menu item configuration provided by the user. The object has the following properties: `title`, `type`, `reference`, `cmsPage`, `nesting`. +* `$item` - the item object (`RainLab\Pages\Classes\MenuItem` when resolving a menu, or `Cms\Models\PageLookupItem` when resolving a page finder link). The item object represents the configuration provided by the user. Both objects expose the following properties: `title`, `type`, `reference`, `cmsPage`, `nesting`. * `$url` - specifies the current absolute URL, in lower case. Always use the `Url::to()` helper to generate menu item links and compare them with the current URL. * `$theme` - the current theme object (`Cms\Classes\Theme`). diff --git a/Plugin.php b/Plugin.php index 7208e66d..24594145 100644 --- a/Plugin.php +++ b/Plugin.php @@ -146,18 +146,11 @@ public function boot() Event::listen('cms.pageLookup.listTypes', function() { return [ 'static-page' => 'Static page', - 'all-static-pages' => ['All static pages', true] + 'all-static-pages' => ['label' => 'All static pages', 'nesting' => true] ]; }); - Event::listen('pages.menuitem.listTypes', function() { - return [ - 'static-page' => 'Static page', - 'all-static-pages' => 'All static pages' - ]; - }); - - Event::listen(['cms.pageLookup.getTypeInfo', 'pages.menuitem.getTypeInfo'], function($type) { + Event::listen('cms.pageLookup.getTypeInfo', function($type) { if ($type == 'url') { return []; } @@ -167,7 +160,7 @@ public function boot() } }); - Event::listen(['cms.pageLookup.resolveItem', 'pages.menuitem.resolveItem'], function($type, $item, $url, $theme) { + Event::listen('cms.pageLookup.resolveItem', function($type, $item, $url, $theme) { if ($type == 'static-page' || $type == 'all-static-pages') { return StaticPage::resolveMenuItem($item, $url, $theme); } @@ -177,6 +170,16 @@ public function boot() Plugin::clearCache(); }); + // Resolve translated static page URLs when switching sites via the site picker + Event::listen('cms.sitePicker.overridePattern', function($page, $pattern, $currentSite, $proposedSite) { + if (isset($page->apiBag['staticPage'])) { + $staticPage = $page->apiBag['staticPage']; + + return $staticPage->getTranslatableUrl($proposedSite) + ?: array_get($staticPage->attributes, 'viewBag.url'); + } + }); + Event::listen('cms.template.processTwigContent', function($template, $dataHolder) { if ($template instanceof \Cms\Classes\Layout) { $dataHolder->content = Controller::instance()->parseSyntaxFields($dataHolder->content); diff --git a/assets/css/editor.css b/assets/css/editor.css index e554b4cc..fe666829 100644 --- a/assets/css/editor.css +++ b/assets/css/editor.css @@ -1,6 +1,58 @@ /* Content-region tabs use the shared backend-tabs component with the "primary" style (modules/backend/vuecomponents/tabs). No plugin CSS needed. */ +/* Edit Menu Item modal: reuses the standard backend popup markup (.modal-content / + .modal-header / .btn-close / .modal-footer) so it inherits the admin popup styling; + only the overlay positioning + footer layout are added here. */ +.pages-menu-modal-overlay { + position: fixed; + inset: 0; + z-index: 600; + overflow: auto; + background: rgba(0, 0, 0, .4); + + .modal-dialog { + margin: 60px auto; + max-width: 640px; + } + + .modal-content { + display: flex; + flex-direction: column; + max-height: calc(100vh - 120px); + } + + .modal-header { + position: relative; + } + + .modal-body { + flex: 1 1 auto; + overflow: auto; + } + + .modal-footer { + display: flex; + align-items: center; + gap: 8px; + padding-top: 15px; + border-top: var(--oc-popup-border); + } + + .pages-menu-modal-footer-spacer { + flex: 1 1 auto; + } + + /* Keeps the modal body height stable while the item form loads. */ + .pages-menu-modal-loading { + min-height: 220px; + } + + .pages-menu-delete-btn:hover { + color: var(--oc-danger-color, #cc3300); + } +} + /* Menu editor: item list + item form panel. */ .pages-menu-editor { .menu-item-row { @@ -91,8 +143,8 @@ .menu-item-title { display: block; - font-size: 13px; - line-height: 16px; + font-size: 14px; + line-height: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -100,8 +152,8 @@ .menu-item-subtitle { display: block; - font-size: 11px; - line-height: 14px; + font-size: 12px; + line-height: 15px; color: var(--oc-text-muted-color, #97a1ab); white-space: nowrap; overflow: hidden; diff --git a/classes/Controller.php b/classes/Controller.php index 4f14caad..172cad66 100644 --- a/classes/Controller.php +++ b/classes/Controller.php @@ -44,6 +44,9 @@ public function initCmsPage($url) return null; } + // Overlay translated content for the active site's locale + $page->applySiteContext(); + $viewBag = $page->viewBag; $cmsPage = CmsPage::inTheme($this->theme); diff --git a/classes/EditorExtension.php b/classes/EditorExtension.php index 43c1208b..ff040bc7 100644 --- a/classes/EditorExtension.php +++ b/classes/EditorExtension.php @@ -135,12 +135,13 @@ public function listVueComponents() /** * getSettingsForms returns the Inspector settings forms per document type. + * Menus have no settings form - the name and code are edited in the + * document header, which hides the Settings toolbar button. */ public function getSettingsForms() { return [ - self::DOCUMENT_TYPE_PAGE => $this->loadLocalizedSettingsFields(\RainLab\Pages\Classes\StaticPage\Fields::class), - self::DOCUMENT_TYPE_MENU => $this->loadLocalizedSettingsFields(\RainLab\Pages\Classes\Menu\Fields::class) + self::DOCUMENT_TYPE_PAGE => $this->loadLocalizedSettingsFields(\RainLab\Pages\Classes\StaticPage\Fields::class) ]; } @@ -175,7 +176,7 @@ public function getNewDocumentsData() ] ); - $description->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small'); + $description->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small cms-page'); $description->setInitialDocumentData([ 'fileName' => '', 'markup' => '', @@ -192,11 +193,13 @@ public function getNewDocumentsData() ] ); - $menuDescription->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small'); + $menuDescription->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small text'); + // No top-level `code` here: the document header only auto-generates the + // code from the name while the code is untouched (undefined). $menuDescription->setInitialDocumentData([ - 'code' => '', + 'name' => __("New menu"), 'items' => [], - 'settings' => ['name' => __("New menu"), 'code' => 'new-menu'] + 'settings' => ['name' => __("New menu")] ]); $contentDescription = new NewDocumentDescription( @@ -209,7 +212,7 @@ public function getNewDocumentsData() ] ); - $contentDescription->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small'); + $contentDescription->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small cms-content'); $contentDescription->setInitialDocumentData([ 'fileName' => '', 'markup' => '', @@ -300,21 +303,24 @@ protected function addContentNavigatorNodes($section, $theme) ->setChildKeyPrefix(self::DOCUMENT_TYPE_CONTENT.':') ->setUserData(['topLevel' => true]); + $this->addNodeCreateMenu($rootNode, self::DOCUMENT_TYPE_CONTENT, __("New Content Block")); + // Folder nodes are cached by path so files sharing a directory nest under one folder. $folderNodes = []; foreach (Content::listInTheme($theme, true) as $content) { $fileName = ltrim($content->fileName, '/'); - // Static page content is managed by the page document type, not as content blocks. - if (starts_with($fileName, 'static-pages/') || starts_with($fileName, 'static-pages-fr/')) { + // Static page content is managed by the page document type, not as content + // blocks. This includes translated mirror directories (static-pages-{locale}). + if (preg_match('#^static-pages(-[^/]+)?/#', $fileName)) { continue; } $parentNode = $this->resolveContentFolderNode($rootNode, $fileName, $folderNodes); $node = $parentNode->addNode(basename($fileName), $fileName); - $node->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small'); + $node->setIcon(self::ICON_COLOR_CONTENT, 'backend-icon-background entity-small cms-content'); } } @@ -338,8 +344,8 @@ protected function resolveContentFolderNode($rootNode, string $fileName, array & $folder = $parentNode->addNode($segment, 'folder:'.$accumulated); $folder ->setDisplayMode(NodeDefinition::DISPLAY_MODE_TREE) - ->setIcon('#c0c0c0', 'backend-icon-background folder-small') ->setUserData(['isFolder' => true]); + $folder->setFolderIcon(); $folderNodes[$accumulated] = $folder; } @@ -359,9 +365,11 @@ protected function addMenusNavigatorNodes($section, $theme) ->setChildKeyPrefix(self::DOCUMENT_TYPE_MENU.':') ->setUserData(['topLevel' => true]); + $this->addNodeCreateMenu($rootNode, self::DOCUMENT_TYPE_MENU, __("New Menu")); + foreach (Menu::listInTheme($theme, true) as $menu) { $node = $rootNode->addNode($menu->name ?: $menu->getBaseFileName(), $menu->getBaseFileName()); - $node->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small'); + $node->setIcon(self::ICON_COLOR_MENU, 'backend-icon-background entity-small text'); } } @@ -379,6 +387,8 @@ protected function addPagesNavigatorNodes($section, $theme) ->setDragAndDropMode([NodeDefinition::DND_SORT, NodeDefinition::DND_MOVE]) ->setUserData(['topLevel' => true]); + $this->addNodeCreateMenu($rootNode, self::DOCUMENT_TYPE_PAGE, __("New Page")); + $pageList = new PageList($theme); $this->addPageTreeNodes($pageList->getPageTree(true), $rootNode); } @@ -394,7 +404,7 @@ protected function addPageTreeNodes($pages, $parentNode) $title = $page->getViewBag()->property('title') ?: $baseName; $node = $parentNode->addNode($title, $baseName); - $node->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small'); + $node->setIcon(self::ICON_COLOR_PAGE, 'backend-icon-background entity-small cms-page'); // path drives the drag-move handler; it identifies which page moved where. $node->setUserData(['path' => $baseName]); @@ -404,6 +414,20 @@ protected function addPageTreeNodes($pages, $parentNode) } } + /** + * addNodeCreateMenu adds a "create document" action to a top-level navigator node, so + * each type (Static Pages / Menus / Content) offers creating a new object in context, + * mirroring the main Editor IDE. + */ + protected function addNodeCreateMenu($node, string $documentType, string $label) + { + $node->addRootMenuItem( + ItemDefinition::TYPE_TEXT, + $label, + 'pages:create-document@'.$documentType + )->setIcon('icon-create'); + } + /** * addSectionMenuItems adds the refresh and create menu items to a section. */ diff --git a/classes/Menu.php b/classes/Menu.php index 42076386..36fdfd3b 100644 --- a/classes/Menu.php +++ b/classes/Menu.php @@ -1,6 +1,7 @@ type, $item, $currentUrl, $this->theme]); + $apiResult = Event::fire('cms.pageLookup.resolveItem', [$item->type, $item, $currentUrl, $this->theme]); if (is_array($apiResult)) { foreach ($apiResult as $itemInfo) { if (!is_array($itemInfo)) { @@ -256,6 +258,12 @@ public function generateReferences($page) $iterator($items); + /* + * Apply per-item locale overrides stored by the menu editor in the item + * view bag (viewBag.locale.{locale}.{field}) for the active site's locale. + */ + $this->applyLocaleOverrides($items); + /* * @event pages.menu.referencesGenerated * Provides opportunity to dynamically change menu entries right after reference generation. @@ -291,4 +299,44 @@ public function generateReferences($page) return $items; } + + /** + * applyLocaleOverrides replaces item fields with translated values stored in + * the item view bag (viewBag.locale.{locale}.{field}), matching the storage + * format used by earlier versions of this plugin. + */ + protected function applyLocaleOverrides($items) + { + if (!Site::hasMultiSite()) { + return; + } + + $site = Site::getActiveSite(); + $primary = Site::getPrimarySite(); + if (!$site || !$primary) { + return; + } + + $locale = (string) $site->hard_locale; + if (!strlen($locale) || $locale === (string) $primary->hard_locale) { + return; + } + + $iterator = function($menuItems) use (&$iterator, $locale) { + foreach ($menuItems as $item) { + $localeFields = array_get($item->viewBag, "locale.{$locale}", []); + foreach ($localeFields as $fieldName => $fieldValue) { + if ($fieldValue) { + $item->$fieldName = $fieldValue; + } + } + + if ($item->items) { + $iterator($item->items); + } + } + }; + + $iterator($items); + } } diff --git a/classes/MenuItem.php b/classes/MenuItem.php index 61b39f71..e1d5c357 100644 --- a/classes/MenuItem.php +++ b/classes/MenuItem.php @@ -1,6 +1,6 @@ 'Header', ]; - $apiResult = Event::fire('pages.menuitem.listTypes'); - - if (is_array($apiResult)) { - foreach ($apiResult as $typeList) { - if (!is_array($typeList)) { - continue; - } - - foreach ($typeList as $typeCode => $typeName) { - $result[$typeCode] = $typeName; - } - } - } + $result += (new PageLookupItem)->getTypeOptions(); return $result; } @@ -173,42 +163,12 @@ public function getReferenceOptions($keyValue = null) } /** - * getTypeInfo returns type information resolved from menu item providers + * getTypeInfo returns type information resolved from page lookup providers + * via the shared page lookup system (cms.pageLookup.getTypeInfo). */ public static function getTypeInfo($type) { - $result = []; - $apiResult = Event::fire('pages.menuitem.getTypeInfo', [$type]); - - if (is_array($apiResult)) { - foreach ($apiResult as $typeInfo) { - if (!is_array($typeInfo)) { - continue; - } - - foreach ($typeInfo as $name => $value) { - if ($name == 'cmsPages') { - $cmsPages = []; - - foreach ($value as $page) { - $baseName = $page->getBaseFileName(); - $pos = strrpos($baseName, '/'); - - $dir = $pos !== false ? substr($baseName, 0, $pos).' / ' : null; - $cmsPages[$baseName] = strlen($page->title) - ? $dir.$page->title - : $baseName; - } - - $value = $cmsPages; - } - - $result[$name] = $value; - } - } - } - - return $result; + return (new PageLookupItem)->getTypeInfo((string) $type); } /** diff --git a/classes/Page.php b/classes/Page.php index 8d8d81fd..5fb5a4b5 100644 --- a/classes/Page.php +++ b/classes/Page.php @@ -1,6 +1,7 @@ getBaseFileName()]); + $this->deleteLocaleMirrors(); + parent::delete(); /* @@ -272,7 +275,7 @@ public static function url($name) return null; } - $url = array_get($page->attributes, 'viewBag.url'); + $url = $page->getTranslatableUrl() ?: array_get($page->attributes, 'viewBag.url'); return Cms::url($url); } @@ -569,6 +572,111 @@ public function setPlaceholdersAttribute($value) $this->attributes['placeholders'] = $placeholders; } + // + // Localization + // + + /** + * @var string|null appliedSiteLocale is set once locale overrides have been applied, + * also used to differentiate the Twig cache between locales. + */ + protected $appliedSiteLocale = null; + + /** + * applySiteContext overlays translated content for the active site's locale. + * + * Translated page content lives in a locale-suffixed mirror directory + * (content/static-pages-{locale}/) controlled entirely by this plugin - the + * core content/{locale}/ convention only applies to the {% content %} tag. + * Mirror values (view bag, markup, placeholders) override the base page + * where present. Translated URLs come from viewBag.localeUrl in the base + * file via the HasTranslatableBag trait. + */ + public function applySiteContext($site = null) + { + if ($this->appliedSiteLocale !== null || !Site::hasMultiSite()) { + return; + } + + $site = $site ?: Site::getActiveSite(); + $primary = Site::getPrimarySite(); + if (!$site || !$primary) { + return; + } + + $locale = (string) $site->hard_locale; + if (!strlen($locale) || $locale === (string) $primary->hard_locale) { + return; + } + + foreach (Site::getLocaleKeyChain($locale) as $localeKey) { + if ($mirror = PageLocale::findLocale($localeKey, $this)) { + $this->applyLocaleMirror($mirror); + $this->appliedSiteLocale = $localeKey; + return; + } + } + + $this->appliedSiteLocale = $locale; + } + + /** + * applyLocaleMirror overlays a translated mirror's values over this page. + */ + protected function applyLocaleMirror(PageLocale $mirror) + { + // Non-empty view bag values override the base. The URL and layout stay + // structural - the URL is resolved via getTranslatableUrl and the layout + // always comes from the base page. + foreach ((array) $mirror->getViewBag()->getProperties() as $name => $value) { + if (in_array($name, ['url', 'layout']) || $value === null || $value === '') { + continue; + } + + $this->getViewBag()->setProperty($name, $value); + } + + $this->fillViewBagArray(); + + if (strlen(trim((string) $mirror->markup))) { + $this->markup = $mirror->markup; + $this->processedMarkupCache = false; + } + + // Placeholder content is stored as {% put %} blocks in the code section + if (strlen(trim((string) $mirror->code))) { + $this->attributes['code'] = $mirror->code; + unset($this->attributes['placeholders']); + } + } + + /** + * getTwigCacheKey differentiates compiled templates per applied locale, since + * translated markup is rendered under the base page's file path. + */ + public function getTwigCacheKey() + { + $key = parent::getTwigCacheKey(); + + if ($this->appliedSiteLocale !== null) { + $key .= '-'.$this->appliedSiteLocale; + } + + return $key; + } + + /** + * deleteLocaleMirrors removes any translated mirror files for this page. + */ + protected function deleteLocaleMirrors() + { + $pattern = $this->theme->getPath().'/content/static-pages-*/'.$this->fileName; + + foreach (File::glob($pattern) ?: [] as $filePath) { + File::delete($filePath); + } + } + /** * getProcessedMarkup will return the processed markup of a page */ @@ -632,9 +740,19 @@ public function getProcessedPlaceholderMarkup($placeholderName, $placeholderCont /** * getMenuCacheKey returns a cache key for this record */ - protected static function getMenuCacheKey($theme) + protected static function getMenuCacheKey($theme, $locale = null) { $key = crc32($theme->getPath()).'static-page-menu'; + + // Menu trees hold translated URLs and titles, cache them per locale + if ($locale === null && Site::hasMultiSite()) { + $locale = Site::getActiveSite()?->hard_locale; + } + + if ($locale) { + $key .= '-'.$locale; + } + /** * @event pages.page.getMenuCacheKey * Enables modifying the key used to reference cached RainLab.Pages menu trees @@ -665,10 +783,19 @@ protected static function urlsAreEqual($url, $other) public static function clearMenuCache($theme) { Cache::forget(self::getMenuCacheKey($theme)); + + // Clear every locale's menu tree + if (Site::hasMultiSite()) { + foreach (Site::listSites() as $site) { + if ($site->hard_locale) { + Cache::forget(self::getMenuCacheKey($theme, $site->hard_locale)); + } + } + } } /** - * getMenuTypeInfo is the handler for the pages.menuitem.getTypeInfo event + * getMenuTypeInfo is the handler for the cms.pageLookup.getTypeInfo event * * The type information is returned as array with the following elements: * - references - a list of the item type reference options. The options are returned in the @@ -702,7 +829,7 @@ public static function getMenuTypeInfo($type) } /** - * resolveMenuItem is the handler for the pages.menuitem.resolveItem event + * resolveMenuItem is the handler for the cms.pageLookup.resolveItem event * * The result is an array with the following keys: * - url - the menu item URL. Not required for menu item types that return all available records. @@ -835,9 +962,13 @@ public static function buildMenuTree($theme) $result = []; foreach ($items as $item) { + // Overlay the active site's translated title and URL, if any + $item->page->applySiteContext(); + $viewBag = $item->page->viewBag; $pageCode = $item->page->getBaseFileName(); - $pageUrl = Str::lower(RouterHelper::normalizeUrl(array_get($viewBag, 'url'))); + $pageUrl = $item->page->getTranslatableUrl() ?: array_get($viewBag, 'url'); + $pageUrl = Str::lower(RouterHelper::normalizeUrl($pageUrl)); $itemData = [ 'url' => $pageUrl, diff --git a/classes/PageLocale.php b/classes/PageLocale.php new file mode 100644 index 00000000..caf639e8 --- /dev/null +++ b/classes/PageLocale.php @@ -0,0 +1,95 @@ +theme, $page->fileName); + }); + } + + /** + * getObjectTypeDirName + */ + public function getObjectTypeDirName() + { + return 'content/static-pages-'.static::$contextLocale; + } + + /** + * beforeCreate keeps the file name assigned by the caller - mirrors always + * share their base page's file name. + */ + public function beforeCreate() + { + } + + /** + * afterCreate does not touch the meta index. + */ + public function afterCreate() + { + } + + /** + * beforeValidate has no unique URL constraints for mirrors. + */ + public function beforeValidate() + { + } + + /** + * appendToMeta is disabled for mirrors. + */ + protected function appendToMeta() + { + } + + /** + * removeFromMeta is disabled for mirrors. + */ + protected function removeFromMeta() + { + } +} diff --git a/classes/Router.php b/classes/Router.php index d08288c8..9aa095f8 100644 --- a/classes/Router.php +++ b/classes/Router.php @@ -1,5 +1,6 @@ getViewBag()->property('url'); + // Prefer the translated URL for the active site, if any + $url = $page->getTranslatableUrl() ?: $page->getViewBag()->property('url'); if (!$url) { continue; } @@ -147,9 +149,19 @@ protected function loadUrlMap() * @param string $keyName Specifies the base key name. * @return string Returns the theme-specific key name. */ - protected function getCacheKey($keyName) + protected function getCacheKey($keyName, $locale = null) { $key = crc32($this->theme->getPath()).$keyName; + + // URL maps hold translated URLs, cache them per locale + if ($locale === null && Site::hasMultiSite()) { + $locale = Site::getActiveSite()?->hard_locale; + } + + if ($locale) { + $key .= '-'.$locale; + } + /** * @event pages.router.getCacheKey * Enables modifying the key used to reference cached RainLab.Pages routes @@ -173,5 +185,14 @@ public function clearCache() self::$cache = []; self::$urlMap = []; Cache::forget($this->getCacheKey('static-page-url-map')); + + // Clear every locale's map + if (Site::hasMultiSite()) { + foreach (Site::listSites() as $site) { + if ($site->hard_locale) { + Cache::forget($this->getCacheKey('static-page-url-map', $site->hard_locale)); + } + } + } } } diff --git a/classes/editorextension/HasMenuCrud.php b/classes/editorextension/HasMenuCrud.php index d044ddae..601887c8 100644 --- a/classes/editorextension/HasMenuCrud.php +++ b/classes/editorextension/HasMenuCrud.php @@ -62,11 +62,20 @@ protected function saveMenuDocument($controller) $settings = (array) array_get($documentData, 'settings', []); $items = array_get($documentData, 'items', []); + $items = is_array($items) ? $this->normalizeItems($items) : []; + + // With a non-primary-locale site selected, posted title/url values are + // stored as per-item locale translations and the base values are kept. + if (strlen($code) && ($locale = $this->getEditLocale())) { + $originalItems = (array) array_get($menu->attributes, 'items', []); + $items = $this->localizeItemData($items, $originalItems, $locale); + } $menu->fill([ 'name' => (string) array_get($settings, 'name'), - 'code' => (string) array_get($settings, 'code', $code), - 'itemData' => is_array($items) ? $this->normalizeItems($items) : [] + // The code is a root document property (edited in the header) + 'code' => (string) (array_get($documentData, 'code') ?: array_get($settings, 'code', $code)), + 'itemData' => $items ]); $menu->save(); @@ -98,10 +107,18 @@ protected function deleteMenuDocument($controller) */ protected function menuToDocumentArray(Menu $menu): array { + $items = $this->itemsToArray($menu->items); + + // When the backend site picker selects a non-primary locale, show that + // locale's item translations (viewBag.locale.{locale}.{field}). + if ($locale = $this->getEditLocale()) { + $items = $this->applyItemsEditLocale($items, $locale); + } + return [ 'name' => $menu->name, 'code' => $menu->getBaseFileName(), - 'items' => $this->itemsToArray($menu->items), + 'items' => $items, 'settings' => [ 'name' => $menu->name, 'code' => $menu->getBaseFileName() @@ -109,6 +126,80 @@ protected function menuToDocumentArray(Menu $menu): array ]; } + /** + * applyItemsEditLocale replaces item fields with their translated values for + * display in the editor, matching the storage format used by earlier versions + * of this plugin (viewBag.locale.{locale}.{field}). + */ + protected function applyItemsEditLocale(array $items, string $locale): array + { + foreach ($items as &$item) { + $localeFields = (array) array_get($item, 'viewBag.locale.'.$locale, []); + + foreach (['title', 'url'] as $fieldName) { + $value = array_get($localeFields, $fieldName); + if ($value !== null && $value !== '' && array_key_exists($fieldName, $item)) { + $item[$fieldName] = $value; + } + } + + if (!empty($item['items']) && is_array($item['items'])) { + $item['items'] = $this->applyItemsEditLocale($item['items'], $locale); + } + } + + return $items; + } + + /** + * localizeItemData stores the posted title/url values as locale translations + * and restores the base values from the menu on disk, mirroring the original + * RainLab.Translate save behavior. The url is only translated for url-type items. + */ + protected function localizeItemData(array $postedItems, array $originalItems, string $locale): array + { + foreach ($postedItems as $index => &$item) { + $original = (array) ($originalItems[$index] ?? []); + $localeData = (array) array_get($original, 'viewBag.locale', []); + + foreach (['title', 'url'] as $fieldName) { + $value = array_get($item, $fieldName); + if ($value === null) { + continue; + } + + // Restore the base value; for items new to this locale session the + // posted value becomes the base value too. + $originalValue = array_get($original, $fieldName, $value); + array_set($item, $fieldName, $originalValue); + + $localeData[$locale][$fieldName] = $value; + } + + // Only url-type items carry a translated URL + if (array_get($item, 'type', 'url') !== 'url') { + foreach ($localeData as &$targetData) { + unset($targetData['url']); + } + unset($targetData); + } + + if ($localeData) { + array_set($item, 'viewBag.locale', $localeData); + } + + if (!empty($item['items']) && is_array($item['items'])) { + $item['items'] = $this->localizeItemData( + $item['items'], + (array) array_get($original, 'items', []), + $locale + ); + } + } + + return $postedItems; + } + /** * normalizeItems recursively coerces boolean flags to the string format menus store. */ diff --git a/classes/editorextension/HasStaticPageCrud.php b/classes/editorextension/HasStaticPageCrud.php index 88a9703c..d6b09a3e 100644 --- a/classes/editorextension/HasStaticPageCrud.php +++ b/classes/editorextension/HasStaticPageCrud.php @@ -1,9 +1,11 @@ pageToDocumentArray($page); + $metadata = $this->pageMetadata($page); + + if ($locale = $this->getEditLocale()) { + $mirror = PageLocale::findLocale($locale, $page); + $document = $this->overlayLocaleDocument($document, $page, $mirror, $locale); + $metadata['locale'] = $locale; + $metadata['mtime'] = $mirror ? $mirror->mtime : null; + } + return [ - 'document' => $this->pageToDocumentArray($page), - 'metadata' => $this->pageMetadata($page) + 'document' => $document, + 'metadata' => $metadata ]; } + /** + * getEditLocale returns the locale being edited when the backend site picker + * has a non-primary-locale site selected, otherwise null. + */ + protected function getEditLocale(): ?string + { + if (!Site::hasMultiSite()) { + return null; + } + + $site = Site::getSiteFromContext(); + $primary = Site::getPrimarySite(); + if (!$site || !$primary || $site->id === $primary->id) { + return null; + } + + $locale = (string) $site->hard_locale; + + return (strlen($locale) && $locale !== (string) $primary->hard_locale) ? $locale : null; + } + + /** + * overlayLocaleDocument overrides the base document values with the translated + * mirror's content. The URL comes from viewBag.localeUrl in the base file. + */ + protected function overlayLocaleDocument(array $document, StaticPage $page, ?PageLocale $mirror, string $locale): array + { + // Translated URL lives in the base page's view bag + $localeUrl = array_get($page->viewBag, 'localeUrl.'.$locale); + if ($localeUrl !== null && $localeUrl !== '') { + $document['url'] = $localeUrl; + $document['settings']['url'] = $localeUrl; + } + + if (!$mirror) { + return $document; + } + + // Mirror view bag values (title, syntax field data) override the base + foreach ((array) $mirror->getViewBag()->getProperties() as $name => $value) { + if (in_array($name, ['url', 'layout']) || $value === null || $value === '') { + continue; + } + + $document['settings'][$name] = $value; + if (array_key_exists($name, $document)) { + $document[$name] = $value; + } + } + + if (strlen(trim((string) $mirror->markup))) { + $document['markup'] = $mirror->markup; + } + + // Mirror placeholders override where present + $mirrorPlaceholders = (array) $mirror->placeholders; + foreach ($mirrorPlaceholders as $code => $content) { + if (array_key_exists($code, (array) $document['placeholders']) && strlen(trim((string) $content))) { + $document['placeholders'][$code] = $content; + } + } + + return $document; + } + /** * savePageDocument creates or updates a static page. */ @@ -42,6 +122,12 @@ protected function savePageDocument($controller) $theme = $this->getEditTheme(); $path = trim((string) array_get($metadata, 'path')); + // Editing an existing page with a non-primary-locale site selected writes + // to that locale's mirror file instead. New pages always create the base. + if (strlen($path) && ($locale = $this->getEditLocale())) { + return $this->saveLocalizedPageDocument($controller, $locale); + } + $page = strlen($path) ? StaticPage::load($theme, $path) : StaticPage::inTheme($theme); @@ -85,6 +171,100 @@ protected function savePageDocument($controller) ]; } + /** + * saveLocalizedPageDocument writes the posted document to the locale's mirror + * file (content/static-pages-{locale}/), leaving the base page untouched except + * for the translated URL, which is stored in the base view bag as localeUrl. + */ + protected function saveLocalizedPageDocument($controller, string $locale) + { + $documentData = (array) post('documentData'); + $metadata = (array) post('documentMetadata'); + $forceSave = (bool) post('documentForceSave'); + + $theme = $this->getEditTheme(); + $path = trim((string) array_get($metadata, 'path')); + + $page = StaticPage::load($theme, $path); + if (!$page) { + throw new SystemException(sprintf('The static page %s was not found.', $path)); + } + + $mirror = PageLocale::findLocale($locale, $page); + + // Concurrency guard against the mirror file + if ( + $mirror && + !$forceSave && + $mirror->mtime && + array_get($metadata, 'mtime') != $mirror->mtime + ) { + return ['mtimeMismatch' => true]; + } + + $settings = $this->cleanSyntaxFieldData((array) array_get($documentData, 'settings', [])); + + // A URL differing from the base URL is stored as localeUrl.{locale} in the + // base file, matching the translated URL storage read by HasTranslatableBag. + $postedUrl = trim((string) array_get($settings, 'url')); + $baseUrl = (string) array_get($page->viewBag, 'url'); + $localeUrls = (array) array_get($page->viewBag, 'localeUrl', []); + $newLocaleUrls = $localeUrls; + + if (strlen($postedUrl) && $postedUrl !== $baseUrl) { + $newLocaleUrls[$locale] = $postedUrl; + } + else { + unset($newLocaleUrls[$locale]); + } + + if ($newLocaleUrls != $localeUrls) { + $baseViewBag = (array) $page->getViewBag()->getProperties(); + $baseViewBag['localeUrl'] = $newLocaleUrls; + + $page->fill(['settings' => ['viewBag' => $baseViewBag]]); + $page->save(); + } + + // Mirrors never store structural fields; the layout is copied from the + // base so placeholder pruning resolves against the correct layout. + unset($settings['url']); + $settings['layout'] = array_get($page->viewBag, 'layout'); + + $fillData = [ + 'settings' => ['viewBag' => $settings], + 'markup' => (string) array_get($documentData, 'markup'), + ]; + + $placeholders = array_get($documentData, 'placeholders'); + if (is_array($placeholders)) { + $fillData['placeholders'] = $placeholders; + } + + $mirror = PageLocale::withLocale($locale, function() use ($theme, $page, $mirror, $fillData) { + if (!$mirror) { + $mirror = PageLocale::inTheme($theme); + $mirror->fileName = $page->fileName; + } + + // Fill the settings first so the layout is resolvable when the + // placeholder fill prunes against the layout's placeholder list. + $mirror->fill(['settings' => $fillData['settings']]); + $mirror->fill(array_diff_key($fillData, ['settings' => true])); + $mirror->save(); + + return $mirror; + }); + + Event::fire('cms.template.save', [$controller, $mirror, 'static-page']); + + $result = $this->pageMetadata($page); + $result['locale'] = $locale; + $result['mtime'] = $mirror->mtime; + + return ['metadata' => $result]; + } + /** * cleanSyntaxFieldData strips repeater bookkeeping keys from posted viewBag data. */ diff --git a/classes/menu/Fields.php b/classes/menu/Fields.php deleted file mode 100644 index 20fff33a..00000000 --- a/classes/menu/Fields.php +++ /dev/null @@ -1,37 +0,0 @@ - [ - 'title' => "Name", - 'placeholder' => "New menu", - 'type' => 'string', - 'validation' => [ - 'required' => ['message' => "The Name is required."] - ] - ], - 'code' => [ - 'title' => "Code", - 'placeholder' => "new-menu", - 'type' => 'string', - 'preset' => ['property' => "name", 'type' => 'file'], - 'validation' => [ - 'required' => ['message' => "The Code is required."], - 'regex' => [ - 'message' => "Invalid Code format. The Code can contain digits, Latin letters and the following symbols: _-", - 'pattern' => '^[0-9a-z\\-\\_]+$' - ] - ] - ] - ]; - } -} diff --git a/classes/staticpage/Fields.php b/classes/staticpage/Fields.php index 6b53c370..5d60078d 100644 --- a/classes/staticpage/Fields.php +++ b/classes/staticpage/Fields.php @@ -7,31 +7,11 @@ class Fields { /** * defineSettingsFields returns the Inspector field definitions for a static page. + * The title and URL are edited in the document header, not here. */ public function defineSettingsFields(): array { return [ - 'title' => [ - 'title' => "Title", - 'placeholder' => "New page title", - 'type' => 'string', - 'validation' => [ - 'required' => ['message' => "The Title is required."] - ] - ], - 'url' => [ - 'title' => "URL", - 'placeholder' => "/", - 'type' => 'string', - 'preset' => ['property' => "title", 'type' => 'url'], - 'validation' => [ - 'required' => ['message' => "The URL is required."], - 'regex' => [ - 'message' => "Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: _-/.", - 'pattern' => '^/[a-z0-9/_\\-\\.]*$' - ] - ] - ], 'fileName' => [ 'title' => "File Name", 'type' => 'string', diff --git a/controllers/index/HasMenuItemForm.php b/controllers/index/HasMenuItemForm.php index 20e8d6c4..c65c2f2a 100644 --- a/controllers/index/HasMenuItemForm.php +++ b/controllers/index/HasMenuItemForm.php @@ -29,7 +29,7 @@ protected function makeMenuItemFormWidget() $config = $this->makeConfig('~/plugins/rainlab/pages/classes/menuitem/fields.yaml'); $config->model = $menuItem; - $config->alias = 'menuItemForm'; + $config->alias = $this->getMenuItemFormAlias(); $config->arrayName = 'menuItem'; $widget = $this->makeWidget(\Backend\Widgets\Form::class, $config); @@ -38,6 +38,18 @@ protected function makeMenuItemFormWidget() return $this->menuItemFormWidget = $widget; } + /** + * getMenuItemFormAlias returns the client-supplied form alias. Each open menu + * document uses its own alias so the generated field ids are unique, keeping + * checkbox labels bound to their own document's inputs. + */ + protected function getMenuItemFormAlias(): string + { + $alias = preg_replace('/[^a-zA-Z0-9]/', '', (string) post('formAlias')); + + return strlen($alias) ? $alias : 'menuItemForm'; + } + /** * onLoadMenuItemForm renders the per-item Form widget over AJAX. */ @@ -45,8 +57,13 @@ public function onLoadMenuItemForm() { $widget = $this->makeMenuItemFormWidget(); + $containerId = preg_replace('/[^a-zA-Z0-9_\-]/', '', (string) post('containerId')); + if (!strlen($containerId)) { + $containerId = 'pagesMenuItemForm'; + } + return [ - '#pagesMenuItemForm' => $widget->render(['useContainer' => false]) + '#'.$containerId => $widget->render(['useContainer' => false]) ]; } @@ -69,7 +86,12 @@ public function onMenuItemReferenceSearch() { $alias = trim((string) post('alias')); - $widget = new MenuItemSearch($this, ['alias' => $alias]); + $formField = new \Backend\Classes\FormField([ + 'fieldName' => 'referenceSearch', + 'arrayName' => 'menuItem' + ]); + + $widget = new MenuItemSearch($this, $formField, ['alias' => $alias]); return $widget->onSearch(); } diff --git a/controllers/index/HasSyntaxFields.php b/controllers/index/HasSyntaxFields.php index 9eb8c170..a452a937 100644 --- a/controllers/index/HasSyntaxFields.php +++ b/controllers/index/HasSyntaxFields.php @@ -36,7 +36,7 @@ protected function makeSyntaxFieldsWidget($path, $tab = null) $config = $this->makeConfig(['fields' => []]); $config->model = $page; - $config->alias = 'pagesSyntaxForm'; + $config->alias = $this->getSyntaxFieldsAlias(); $config->arrayName = 'syntaxFields'; $config->context = $page->exists ? 'update' : 'create'; @@ -89,6 +89,19 @@ protected function addPageSyntaxFields($formWidget, $page, $tab = null) } } + /** + * getSyntaxFieldsAlias returns the client-supplied form alias. Each open page + * document uses its own alias so the generated field ids are unique, keeping + * checkbox labels bound to their own document's inputs. The alias also rides + * along on nested widget AJAX requests via the pagesSyntaxAlias hidden field. + */ + protected function getSyntaxFieldsAlias(): string + { + $alias = preg_replace('/[^a-zA-Z0-9]/', '', (string) (post('formAlias') ?: post('pagesSyntaxAlias'))); + + return strlen($alias) ? $alias : 'pagesSyntaxForm'; + } + /** * bindSyntaxFieldsWidget rebuilds the widget on any request carrying a page path. * @@ -124,7 +137,8 @@ public function onLoadSyntaxFields() // Hidden fields let the per-request rebind (bindSyntaxFieldsWidget) rebuild the exact // same widget so nested repeater/mediafinder AJAX handlers resolve. $hidden = '' - .''; + .'' + .''; return [ '#'.$containerId => $hidden.$widget->render(['useContainer' => false]) diff --git a/lang/en.json b/lang/en.json index 6c7f480c..62edcd7f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -55,6 +55,7 @@ "Error loading menu": "Error loading menu", "Error loading content block": "Error loading content block", "Add item": "Add item", + "Add subitem": "Add subitem", "New menu item": "New menu item", "Untitled": "Untitled", "Up": "Up", @@ -71,5 +72,8 @@ "Move down": "Move down", "Apply": "Apply", "Cancel": "Cancel", - "Error loading page": "Error loading page" + "Error loading page": "Error loading page", + "New Page": "New Page", + "New Menu": "New Menu", + "New Content Block": "New Content Block" } diff --git a/vuecomponents/menueditor/assets/js/menueditor.js b/vuecomponents/menueditor/assets/js/menueditor.js index b2d96bb1..91ce09b3 100644 --- a/vuecomponents/menueditor/assets/js/menueditor.js +++ b/vuecomponents/menueditor/assets/js/menueditor.js @@ -1,15 +1,25 @@ import { DocumentComponentBase } from '../../../../../../../modules/editor/assets/js/editor.extension.documentcomponent.base.js'; +// Each open menu document renders its own item form island; a per-instance +// counter keeps the container id and form widget alias (which prefixes every +// field id) unique, so checkbox labels always target their own tab's inputs. +let menuItemFormUid = 0; + export default { extends: DocumentComponentBase, data: function() { + const uid = ++menuItemFormUid; + return { documentSettingsPopupTitle: this.trans('Menu') || 'Menu', documentTitleProperty: 'name', + menuItemFormContainerId: 'pagesMenuItemForm' + uid, + menuItemFormAlias: 'menuItemForm' + uid, items: [], selectedItem: null, itemFormLoaded: false, modalVisible: false, + modalLoading: false, newItemTitle: this.trans('New menu item'), nextItemId: 1, dragItemId: null, @@ -77,6 +87,20 @@ export default { ]; } }, + watch: { + // The header subtitle edits the top-level code; saving and the settings + // popup read settings.code - keep the two in sync both ways. + 'documentData.code': function(value) { + if (this.documentData && this.documentData.settings && this.documentData.settings.code !== value) { + this.documentData.settings.code = value; + } + }, + 'documentData.settings.code': function(value) { + if (this.documentData && this.documentData.code !== value) { + this.documentData.code = value; + } + } + }, methods: { getRootProperties: function() { return ['code', 'items']; @@ -182,6 +206,18 @@ export default { this.editItem(item); }, + // Creates a new child item under the given row and opens it for editing, + // matching the original plugin's "Add subitem" action. + addSubItem: function(entry) { + const item = this.newBlankItem(); + if (!entry.item._children) { + entry.item._children = []; + } + entry.item._children.push(item); + this.syncItemsToDocument(); + this.editItem(item); + }, + deleteItem: function(item, list) { const arr = list || this.items; const idx = arr.indexOf(item); @@ -194,6 +230,25 @@ export default { } }, + // Delete the item open in the modal, wherever it lives in the tree, then close. + deleteSelectedItem: function() { + const target = this.selectedItem; + if (!target) { + return; + } + const removeFrom = (list) => { + const idx = list.indexOf(target); + if (idx !== -1) { + list.splice(idx, 1); + return true; + } + return list.some((item) => item._children && removeFrom(item._children)); + }; + removeFrom(this.items); + this.closeModal(); + this.syncItemsToDocument(); + }, + moveItemUp: function(entry) { const arr = entry.siblings; const idx = arr.indexOf(entry.item); @@ -379,6 +434,7 @@ export default { this.selectedItem = item; item._selected = true; this.modalVisible = true; + this.modalLoading = true; this.$nextTick(() => { this.loadItemForm(item); @@ -387,27 +443,46 @@ export default { closeModal: function() { this.modalVisible = false; + + // The row highlight only marks the item being edited - clear it when + // the modal closes. + if (this.selectedItem) { + this.selectedItem._selected = false; + this.selectedItem = null; + } }, applyAndClose: function() { this.applyItemForm(); - this.modalVisible = false; + this.closeModal(); }, loadItemForm: function(item) { const container = this.$refs.menuItemForm; if (!container) { + this.modalLoading = false; return; } + // The form is hidden behind a loading indicator until it is fully + // rendered, populated and the type field visibility applied - otherwise + // the previous item's content flashes and the fields jump around. oc.request(container, 'onLoadMenuItemForm', { - data: { bindMenuItemForm: 1 } + data: { + bindMenuItemForm: 1, + containerId: this.menuItemFormContainerId, + formAlias: this.menuItemFormAlias + } }).then(() => { this.itemFormLoaded = true; this.populateItemForm(item); this.bindTypeChange(); - this.refreshReferenceOptions(item.reference, item.cmsPage); - }); + this.bindReferenceSearch(); + return this.refreshReferenceOptions(item.reference, item.cmsPage); + }).then( + () => { this.modalLoading = false; }, + () => { this.modalLoading = false; } + ); }, // Wire the Type dropdown so switching type reloads the reference/cmsPage options, @@ -426,20 +501,58 @@ export default { } }, - // Fetch type info for the current type and (re)populate the reference + cmsPage - // dropdowns, selecting the given values when present. - refreshReferenceOptions: function(selectedReference, selectedCmsPage) { + // Wire the "Search all references" field so picking a result (value format + // "type::reference" from MenuItemSearch) populates the Type + Reference fields, + // matching the original plugin behavior. + bindReferenceSearch: function() { const form = this.$refs.menuItemForm; if (!form) { return; } + const searchInput = form.querySelector('[name="referenceSearch"]'); + if (searchInput && !searchInput._pagesSearchBound) { + searchInput._pagesSearchBound = true; + searchInput.addEventListener('change', () => { + const value = searchInput.value || ''; + const pos = value.indexOf('::'); + if (pos === -1) { + return; + } + + const type = value.substring(0, pos); + const reference = value.substring(pos + 2); + + const typeInput = form.querySelector('[name="menuItem[type]"]'); + if (typeInput && typeInput.value !== type) { + typeInput.value = type; + // Update the select2 display without firing regular change + // handlers, the type cascade would clear the selection made here. + if (window.jQuery) { + window.jQuery(typeInput).trigger('change.select2'); + } + } + + this.refreshReferenceOptions(reference, ''); + }); + } + }, + + // Fetch type info for the current type, (re)populate the reference + cmsPage + // dropdowns, and restrict field visibility to what the type supports - the + // same rules the original plugin applied (url only for the url type, reference/ + // cmsPage/nesting/replace only when the type info advertises them). + refreshReferenceOptions: function(selectedReference, selectedCmsPage) { + const form = this.$refs.menuItemForm; + if (!form) { + return Promise.resolve(); + } const typeInput = form.querySelector('[name="menuItem[type]"]'); const type = typeInput ? typeInput.value : ''; if (!type) { - return; + return Promise.resolve(); } - oc.request(form, 'onGetMenuItemTypeInfo', { + return oc.request(form, 'onGetMenuItemTypeInfo', { data: { type: type } }).then((data) => { const info = (data && data.menuItemTypeInfo) || {}; @@ -453,9 +566,33 @@ export default { info.cmsPages || {}, selectedCmsPage ); + + this.applyTypeFieldVisibility(type, info); }); }, + // Show only the fields relevant to the selected type, mirroring the original + // plugin's applyTypeInfo behavior. + applyTypeFieldVisibility: function(type, info) { + const form = this.$refs.menuItemForm; + if (!form) { + return; + } + + const toggleGroup = (name, visible) => { + const group = form.querySelector('[data-field-name="' + name + '"]'); + if (group) { + group.style.display = visible ? '' : 'none'; + } + }; + + toggleGroup('url', type === 'url'); + toggleGroup('reference', !!info.references); + toggleGroup('cmsPage', !!info.cmsPages); + toggleGroup('nesting', !!info.nesting); + toggleGroup('replace', !!info.dynamicItems); + }, + // Flatten the (possibly nested) references structure into a flat {key: label} map. flattenReferences: function(references) { const flat = {}; @@ -496,20 +633,42 @@ export default { return; } - Object.keys(item).forEach((key) => { - if (key.charAt(0) === '_' || key === 'typeLabel') { + // Checkboxes must be matched explicitly - October renders a hidden + // "0" input with the same name before each checkbox, which a plain + // name selector would match instead. + const setField = (name, value) => { + const checkbox = form.querySelector('input[type="checkbox"][name="' + name + '"]'); + if (checkbox) { + checkbox.checked = !!value && value !== '0'; + checkbox.dispatchEvent(new Event('change', { bubbles: true })); return; } - const input = form.querySelector('[name="menuItem[' + key + ']"]'); + + const input = form.querySelector('[name="' + name + '"]'); if (input) { - if (input.type === 'checkbox') { - input.checked = !!item[key] && item[key] !== '0'; - } - else { - input.value = item[key]; - } + input.value = (value === null || value === undefined) ? '' : value; input.dispatchEvent(new Event('change', { bubbles: true })); } + }; + + Object.keys(item).forEach((key) => { + if (key.charAt(0) === '_' || key === 'typeLabel') { + return; + } + + // View bag values map to nested field names; the locale key holds + // per-locale translations, not form fields. + if (key === 'viewBag' && item.viewBag && typeof item.viewBag === 'object') { + Object.keys(item.viewBag).forEach((vbKey) => { + if (vbKey === 'locale') { + return; + } + setField('menuItem[viewBag][' + vbKey + ']', item.viewBag[vbKey]); + }); + return; + } + + setField('menuItem[' + key + ']', item[key]); }); }, @@ -532,7 +691,9 @@ export default { this.selectedItem[m[1]] = value; } } - this.selectedItem.viewBag = viewBag; + // Merge over the existing view bag - keys not represented in the form + // (e.g. the nested locale translations) must survive the round trip. + this.selectedItem.viewBag = Object.assign({}, this.selectedItem.viewBag, viewBag); // Refresh the row subtitle from the (possibly changed) type. const typeInput = form.querySelector('[name="menuItem[type]"]'); diff --git a/vuecomponents/menueditor/partials/_menueditor.php b/vuecomponents/menueditor/partials/_menueditor.php index 21660f2f..eb2fe141 100644 --- a/vuecomponents/menueditor/partials/_menueditor.php +++ b/vuecomponents/menueditor/partials/_menueditor.php @@ -10,6 +10,10 @@