From 8a2163bb34789f92900a7c82f48efe1723c3a43a Mon Sep 17 00:00:00 2001 From: tiamak Date: Mon, 13 Apr 2026 13:34:38 +0000 Subject: [PATCH] Refine mega menu admin and responsive navigation --- advancedmegamenu.php | 4 + views/css/admin.css | 359 ++++++++- views/css/megamenu.css | 398 +++++++++- views/js/admin.js | 1127 ++++++++++++++++++--------- views/js/megamenu.js | 2 +- views/templates/admin/configure.tpl | 190 ++++- views/templates/hook/megamenu.tpl | 79 +- 7 files changed, 1703 insertions(+), 456 deletions(-) diff --git a/advancedmegamenu.php b/advancedmegamenu.php index cb1da6e..d70c1c4 100644 --- a/advancedmegamenu.php +++ b/advancedmegamenu.php @@ -716,6 +716,10 @@ class AdvancedMegaMenu extends Module implements WidgetInterface switch ($type) { case 'category': + if ($customLink !== '') { + return $customLink; + } + return $entityId > 0 ? $this->context->link->getCategoryLink($entityId) : '#'; case 'cms': if ($entityId <= 0) { diff --git a/views/css/admin.css b/views/css/admin.css index 26997eb..e9eac7c 100644 --- a/views/css/admin.css +++ b/views/css/admin.css @@ -1,4 +1,16 @@ +.adv-megamenu-admin__intro { + margin-bottom: 1.5rem; +} + +.adv-megamenu-admin__intro-rule { + color: #8a6d3b; +} + .adv-megamenu-admin__toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; margin-bottom: 1rem; } @@ -7,64 +19,309 @@ gap: 1rem; } -.adv-menu-node { - border: 1px solid #d9dee3; - border-radius: 8px; - background: #fff; +.adv-megamenu-admin__empty { + padding: 2rem; + border: 1px dashed #c8d2dc; + border-radius: 10px; + background: #fafbfd; + color: #6c7a89; + text-align: center; } -.adv-menu-node__header, -.adv-menu-layout__header { +.adv-menu-admin-root-list, +.adv-menu-admin-children-list, +.adv-menu-admin-layout-list { + display: grid; + gap: 1rem; +} + +.adv-menu-admin-root-shell { + display: grid; + gap: 1rem; +} + +.adv-menu-admin-root-tabs { + display: flex; + gap: 0.75rem; + overflow-x: auto; + padding-bottom: 0.25rem; +} + +.adv-menu-admin-root-tab { display: flex; align-items: center; justify-content: space-between; gap: 1rem; - padding: 0.9rem 1rem; - background: #f7f8f9; - border-bottom: 1px solid #e7eaee; + min-width: 240px; + padding: 0.85rem 1rem; + border: 1px solid #d9dee3; + border-radius: 12px; + background: linear-gradient(180deg, #ffffff 0%, #f7fafc 100%); + text-align: left; + box-shadow: 0 4px 14px rgba(32, 43, 56, 0.04); } -.adv-menu-node__body, -.adv-menu-layout__body { - padding: 1rem; +.adv-menu-admin-root-tab.is-active { + border-color: #5c7c9c; + background: linear-gradient(135deg, #eef4fa 0%, #dfeaf5 100%); + box-shadow: 0 8px 22px rgba(92, 124, 156, 0.18); } -.adv-menu-node__grid, -.adv-menu-layout__grid { - display: grid; - gap: 1rem; - grid-template-columns: repeat(2, minmax(0, 1fr)); +.adv-menu-admin-root-tab__main { + min-width: 0; } -.adv-menu-node__actions, -.adv-menu-layout__actions { +.adv-menu-admin-root-tab__title { + display: block; + font-weight: 600; + color: #22303c; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.adv-menu-admin-root-tab__meta { + display: block; + margin-top: 0.25rem; + color: #66788a; + font-size: 12px; +} + +.adv-menu-admin-root-tab__actions { + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.adv-menu-admin-root-tab__actions .btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 28px; + padding: 0; +} + +.adv-menu-admin-root-panel { + min-width: 0; +} + +.adv-menu-admin-node { + border: 1px solid #d9dee3; + border-radius: 10px; + background: #fff; + box-shadow: 0 4px 14px rgba(32, 43, 56, 0.04); +} + +.adv-menu-admin-node--root > .adv-menu-admin-node__header { + background: linear-gradient(135deg, #f8fafc 0%, #eef3f8 100%); +} + +.adv-menu-admin-node__header, +.adv-menu-admin-layout__header { display: flex; - gap: 0.5rem; - flex-wrap: wrap; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; + padding: 1rem 1.1rem; + border-bottom: 1px solid #edf1f4; } -.adv-menu-node__children, -.adv-menu-node__layouts { +.adv-menu-admin-node__header-main, +.adv-menu-admin-layout__header-main { + min-width: 0; +} + +.adv-menu-admin-node__title, +.adv-menu-admin-layout__title { + display: flex; + align-items: center; + gap: 0.55rem; + margin: 0; + font-size: 16px; + line-height: 1.2; +} + +.adv-menu-admin-node__title-text, +.adv-menu-admin-layout__title-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.adv-menu-admin-node__meta, +.adv-menu-admin-layout__meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.6rem; +} + +.adv-menu-admin-node__meta-item, +.adv-menu-admin-layout__meta-item { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.2rem 0.55rem; + border-radius: 999px; + background: #f1f5f8; + color: #52606d; + font-size: 12px; +} + +.adv-menu-admin-node__actions, +.adv-menu-admin-layout__actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.45rem; +} + +.adv-menu-admin-action { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.adv-menu-admin-node__body, +.adv-menu-admin-layout__body { + padding: 1rem 1.1rem 1.1rem; +} + +.adv-menu-admin-node__summary { + display: grid; + gap: 0.75rem; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; +} + +.adv-menu-admin-node__link { + display: flex; + align-items: center; + gap: 0.5rem; + color: #495867; + font-size: 13px; + word-break: break-all; +} + +.adv-menu-admin-node__image, +.adv-menu-image-preview { + max-width: 140px; + border-radius: 8px; + border: 1px solid #edf1f4; +} + +.adv-menu-admin-node__collections { display: grid; gap: 1rem; margin-top: 1rem; } -.adv-menu-node__children { - padding-left: 1.25rem; - border-left: 2px solid #e7eaee; +.adv-menu-admin-node__children { + padding-left: 1rem; + border-left: 2px solid #edf1f4; } -.adv-menu-language { - border: 1px dashed #d9dee3; - border-radius: 6px; - padding: 0.8rem; +.adv-menu-admin-node__children--grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + align-items: start; } -.adv-menu-image-preview { - max-width: 180px; - margin-top: 0.6rem; - border-radius: 6px; +.adv-menu-admin-node--compact .adv-menu-admin-node__header { + padding: 0.8rem 0.9rem; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__body { + padding: 0.85rem 0.9rem 0.9rem; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__title { + font-size: 14px; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__summary { + grid-template-columns: 1fr; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__actions { + gap: 0.35rem; +} + +.adv-menu-admin-node--compact .adv-menu-admin-action { + padding-left: 0.45rem; + padding-right: 0.45rem; +} + +.adv-menu-admin-node--compact .adv-menu-admin-action span { + display: none; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__meta { + gap: 0.35rem; +} + +.adv-menu-admin-node--compact .adv-menu-admin-node__meta-item { + font-size: 11px; +} + +.adv-menu-admin-node__section-title { + display: flex; + align-items: center; + gap: 0.45rem; + margin: 0 0 0.75rem; + color: #2f3d4a; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.adv-menu-admin-layout { + border: 1px solid #e3e8ed; + border-radius: 8px; + background: #fbfcfd; +} + +.adv-megamenu-admin__modal-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.adv-megamenu-admin__checkbox { + display: flex; + align-items: center; + gap: 0.6rem; + padding-top: 2rem; +} + +.adv-megamenu-admin__checkbox input { + margin: 0; +} + +.adv-megamenu-admin__modal-image { + margin-top: 1.25rem; +} + +.adv-megamenu-admin__lang-panels { + display: grid; + gap: 1rem; + margin-top: 1.25rem; +} + +.adv-megamenu-admin__lang-panel { + border: 1px solid #e3e8ed; + border-radius: 8px; + padding: 1rem; + background: #fcfdff; +} + +.adv-megamenu-admin__lang-panel h5 { + margin: 0 0 0.85rem; +} + +.adv-megamenu-admin__lang-grid { + display: grid; + gap: 1rem; } .adv-menu-products { @@ -83,6 +340,13 @@ background: #eef3f7; } +.adv-menu-product-chip button { + border: 0; + background: transparent; + padding: 0; + line-height: 1; +} + .adv-menu-product-results { margin-top: 0.5rem; border: 1px solid #d9dee3; @@ -104,9 +368,32 @@ border-top: 1px solid #eef2f5; } +.adv-menu-product-results button:hover { + background: #f6f9fc; +} + @media (max-width: 991px) { - .adv-menu-node__grid, - .adv-menu-layout__grid { + .adv-menu-admin-node__summary, + .adv-megamenu-admin__modal-grid { + grid-template-columns: 1fr; + } + + .adv-menu-admin-node__actions, + .adv-menu-admin-layout__actions { + justify-content: flex-start; + } + + .adv-menu-admin-root-tab { + min-width: 200px; + } + + .adv-menu-admin-node__children--grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 767px) { + .adv-menu-admin-node__children--grid { grid-template-columns: 1fr; } } diff --git a/views/css/megamenu.css b/views/css/megamenu.css index 1bd0f07..ab99202 100644 --- a/views/css/megamenu.css +++ b/views/css/megamenu.css @@ -1,4 +1,26 @@ .adv-megamenu { + --adv-root-gap: 0.25rem; + --adv-root-link-gap: 0.45rem; + --adv-root-link-min-height: 50px; + --adv-root-link-padding-x: 0.7rem; + --adv-root-font-size: 0.9rem; + --adv-panel-inner-gap: 1.25rem; + --adv-panel-inner-padding: 1rem; + --adv-panel-nav-min: 240px; + --adv-panel-nav-max: 280px; + --adv-submenu-title-size: 1rem; + --adv-rich-intro-gap: 0.9rem; + --adv-rich-intro-padding: 0.95rem; + --adv-rich-intro-media-width: 180px; + --adv-rich-intro-title-size: 1.12rem; + --adv-card-title-size: 0.95rem; + --adv-card-text-size: 0.88rem; + --adv-product-name-size: 0.8rem; + --adv-node-card-image-size: 88px; + --adv-node-card-body-padding: 0.5rem; + --adv-node-card-gap: 0.3rem; + --adv-node-card-title-lines: 2; + --adv-node-card-text-lines: 3; position: relative; z-index: 500; color: #2d241d; @@ -19,18 +41,36 @@ .adv-megamenu__desktop { position: relative; - display: inline-block; + display: inline-flex; + align-items: center; + gap: 0.6rem; width: auto; max-width: 100%; color: #2d241d; overflow: visible; } +.adv-megamenu__brand { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + text-decoration: none; +} + +.adv-megamenu__brand img { + display: block; + width: auto; + height: auto; + max-width: 50px; + max-height: 50px; +} + .adv-megamenu__root-list { width: auto; display: flex; align-items: stretch; - gap: 0.25rem; + gap: var(--adv-root-gap); flex-wrap: nowrap; white-space: nowrap; padding: 0 0.25rem; @@ -45,11 +85,12 @@ .adv-megamenu .adv-megamenu__root-link:visited { display: flex; align-items: center; - gap: 0.55rem; - min-height: 58px; - padding: 0 1rem; + gap: var(--adv-root-link-gap); + min-height: var(--adv-root-link-min-height); + padding: 0 var(--adv-root-link-padding-x); color: #2d241d; font-weight: 600; + font-size: var(--adv-root-font-size); text-decoration: none; background: transparent; } @@ -99,9 +140,11 @@ .adv-megamenu__panel-inner { display: grid; - grid-template-columns: minmax(280px, 320px) 1fr; - gap: 2rem; - padding: 1.5rem; + grid-template-columns: minmax(var(--adv-panel-nav-min), var(--adv-panel-nav-max)) 1fr; + gap: var(--adv-panel-inner-gap); + padding: var(--adv-panel-inner-padding); + max-height: calc(100vh - 110px); + overflow-y: auto; background: #fffdfa; color: #2d241d; border: 1px solid #ddd3c3; @@ -165,7 +208,7 @@ .adv-megamenu__submenu-title { margin: 0 0 1rem; - font-size: 1.1rem; + font-size: var(--adv-submenu-title-size); font-weight: 700; color: #241a13; } @@ -173,16 +216,16 @@ .adv-megamenu__rich-intro { display: flex; align-items: flex-start; - gap: 1.25rem; + gap: var(--adv-rich-intro-gap); margin-bottom: 1.5rem; - padding: 1.25rem; + padding: var(--adv-rich-intro-padding); border-radius: 18px; background: #f7f1ea; } .adv-megamenu__rich-intro-media { - flex: 0 0 220px; - max-width: 220px; + flex: 0 0 var(--adv-rich-intro-media-width); + max-width: var(--adv-rich-intro-media-width); } .adv-megamenu__rich-intro-media img { @@ -202,12 +245,13 @@ .adv-megamenu__rich-intro-title { margin: 0; - font-size: 1.35rem; + font-size: var(--adv-rich-intro-title-size); font-weight: 700; color: #241a13; } .adv-megamenu__rich-intro-description { + font-size: var(--adv-card-text-size); color: #5a4b3f; line-height: 1.6; } @@ -226,6 +270,80 @@ text-decoration: none; } +.adv-megamenu__rich-node-row { + display: grid; + gap: 0.85rem; + margin-bottom: 1rem; +} + +.adv-megamenu__rich-node-card { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 1rem; + padding: 1rem; + border: 1px solid #ece4d8; + border-radius: 16px; + background: #ffffff; + box-shadow: 0 8px 20px rgba(45, 36, 29, 0.06); +} + +.adv-megamenu__rich-node-media { + flex: 0 0 148px; + max-width: 148px; +} + +.adv-megamenu__rich-node-media img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 12px; +} + +.adv-megamenu__rich-node-body { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-items: flex-start; + gap: 0.55rem; + min-width: 0; +} + +.adv-megamenu__rich-node-title { + margin: 0; + font-size: 1rem; + font-weight: 700; + line-height: 1.3; +} + +.adv-megamenu .adv-megamenu__rich-node-title a, +.adv-megamenu .adv-megamenu__rich-node-title a:link, +.adv-megamenu .adv-megamenu__rich-node-title a:visited { + color: #241a13; + text-decoration: none; +} + +.adv-megamenu .adv-megamenu__rich-node-title a:hover { + color: #8a3c1f; +} + +.adv-megamenu__rich-node-description { + color: #5a4b3f; + font-size: 0.92rem; + line-height: 1.55; +} + +.adv-megamenu .adv-megamenu__rich-node-link, +.adv-megamenu .adv-megamenu__rich-node-link:link, +.adv-megamenu .adv-megamenu__rich-node-link:visited { + display: inline-flex; + align-items: center; + justify-content: center; + margin-top: auto; + text-decoration: none; +} + .adv-megamenu .adv-megamenu__submenu-pane, .adv-megamenu .adv-megamenu__submenu-pane a, .adv-megamenu .adv-megamenu__submenu-pane a:link, @@ -330,13 +448,14 @@ .adv-megamenu__layout-card .card-title { margin: 0; - font-size: 1.1rem; + font-size: var(--adv-card-title-size); font-weight: 700; color: #241a13; } .adv-megamenu__layout-card .card-text { margin: 0; + font-size: var(--adv-card-text-size); color: #5a4b3f; line-height: 1.5; } @@ -381,7 +500,7 @@ } .adv-megamenu__product-name { - font-size: 0.9rem; + font-size: var(--adv-product-name-size); line-height: 1.35; } @@ -397,12 +516,11 @@ .adv-megamenu__toggle { display: inline-flex; align-items: center; - gap: 0.5rem; - padding: 0.8rem 1rem; + justify-content: center; + padding: 0; border: 0; - background: #f3efe8; - color: #2d241d; - font-weight: 700; + background: transparent; + color: inherit; } .adv-megamenu__overlay { @@ -484,6 +602,27 @@ background: #fffdfa; } +.adv-megamenu__mobile-link-row--with-image { + grid-template-columns: 112px minmax(0, 1fr) auto; + column-gap: 0.75rem; +} + +.adv-megamenu__mobile-thumb { + width: 112px; + height: 112px; + overflow: hidden; + border-radius: 10px; + background: #ffffff; + border: 1px solid #ece4d8; +} + +.adv-megamenu__mobile-thumb img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + .adv-megamenu .adv-megamenu__mobile-link-row a, .adv-megamenu .adv-megamenu__mobile-link-row a:link, .adv-megamenu .adv-megamenu__mobile-link-row a:visited { @@ -492,9 +631,28 @@ } @media (max-width: 991px) { - .adv-megamenu { - display: block; + #header.header.js-sticky-header { + position: sticky; + top: 0; + z-index: 1600; width: 100%; + background-color: inherit; + } + + .header-bottom { + box-shadow: 0 8px 18px rgba(17, 17, 17, 0.12); + } + + .header-bottom__container { + position: relative; + z-index: 1; + } + + .adv-megamenu { + display: inline-flex; + width: auto; + max-width: none; + vertical-align: middle; } .adv-megamenu__desktop { @@ -502,7 +660,48 @@ } .adv-megamenu__mobile { - display: block; + display: flex; + flex: 0 0 auto; + width: auto; + order: 2; + align-items: center; + gap: 0.4rem; + } + + .adv-megamenu__brand--mobile img { + max-width: 42px; + max-height: 42px; + } + + .header-bottom__row > .search__mobile { + order: 3; + } + + .header-bottom__row > #_mobile_user_info { + order: 4; + } + + .header-bottom__row > #_mobile_cart { + order: 5; + } + + .adv-megamenu__toggle.header-block__action-btn { + min-height: 100%; + padding: 0.5rem; + } + + .header-bottom__row > .adv-megamenu, + .header-bottom__row > .search__mobile, + .header-bottom__row > #_mobile_user_info, + .header-bottom__row > #_mobile_cart { + align-self: center; + } + + .header-bottom__row > .adv-megamenu .header-block__icon, + .header-bottom__row > .search__mobile .header-block__icon, + .header-bottom__row > #_mobile_user_info .header-block__icon, + .header-bottom__row > #_mobile_cart .header-block__icon { + line-height: 1; } .adv-megamenu__rich-intro { @@ -517,20 +716,22 @@ .adv-megamenu__node-card-row { - row-gap: 1rem; + row-gap: 0.5rem; } .adv-megamenu__node-card { height: 100%; border: 1px solid #ebe2d6; - box-shadow: 0 10px 24px rgba(45, 36, 29, 0.08); + box-shadow: 0 6px 16px rgba(45, 36, 29, 0.07); background: #fffdfa; } .adv-megamenu__node-card .card-img-top { display: block; - width: 250px; - height: 250px; + width: var(--adv-node-card-image-size) !important; + height: var(--adv-node-card-image-size) !important; + min-height: 0 !important; + max-height: var(--adv-node-card-image-size) !important; max-width: 100%; margin: 0 auto; object-fit: contain; @@ -540,7 +741,8 @@ .adv-megamenu__node-card .card-body { display: flex; flex-direction: column; - gap: 0.65rem; + gap: var(--adv-node-card-gap); + padding: var(--adv-node-card-body-padding) !important; } .adv-megamenu__node-card .card-title, @@ -550,9 +752,14 @@ .adv-megamenu__node-card .card-title { margin: 0; - font-size: 1rem; + font-size: var(--adv-card-title-size); font-weight: 700; text-align: center; + line-height: 1.2; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--adv-node-card-title-lines); + overflow: hidden; } .adv-megamenu .adv-megamenu__node-card .card-title a, @@ -568,7 +775,13 @@ .adv-megamenu__node-card .card-text { margin: 0; + font-size: var(--adv-card-text-size); line-height: 1.45; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--adv-node-card-text-lines); + overflow: hidden; + text-align: center; } .adv-megamenu__submenu-pane, @@ -581,3 +794,128 @@ .adv-megamenu__layout-card .card-text { white-space: normal; } + +@media (min-width: 992px) and (max-width: 1382px) { + .adv-megamenu { + --adv-root-gap: clamp(0.12rem, 0.08rem + 0.2vw, 0.25rem); + --adv-root-link-gap: clamp(0.35rem, 0.22rem + 0.22vw, 0.55rem); + --adv-root-link-min-height: clamp(44px, 41px + 0.7vw, 50px); + --adv-root-link-padding-x: clamp(0.45rem, 0.15rem + 0.42vw, 0.7rem); + --adv-root-font-size: clamp(0.78rem, 0.6rem + 0.28vw, 0.9rem); + --adv-panel-inner-gap: clamp(0.85rem, 0.45rem + 0.45vw, 1.25rem); + --adv-panel-inner-padding: clamp(0.75rem, 0.45rem + 0.35vw, 1rem); + --adv-panel-nav-min: clamp(210px, 17vw, 240px); + --adv-panel-nav-max: clamp(225px, 20vw, 280px); + --adv-submenu-title-size: clamp(0.9rem, 0.72rem + 0.2vw, 1rem); + --adv-rich-intro-gap: clamp(0.7rem, 0.45rem + 0.25vw, 0.9rem); + --adv-rich-intro-padding: clamp(0.75rem, 0.45rem + 0.28vw, 0.95rem); + --adv-rich-intro-media-width: clamp(140px, 13vw, 180px); + --adv-rich-intro-title-size: clamp(0.98rem, 0.8rem + 0.24vw, 1.12rem); + --adv-card-title-size: clamp(0.86rem, 0.72rem + 0.2vw, 0.95rem); + --adv-card-text-size: clamp(0.78rem, 0.68rem + 0.16vw, 0.88rem); + --adv-product-name-size: clamp(0.74rem, 0.66rem + 0.12vw, 0.8rem); + --adv-node-card-image-size: clamp(64px, 6vw, 88px); + --adv-node-card-body-padding: clamp(0.2rem, 0.15rem + 0.06vw, 0.3rem); + --adv-node-card-gap: clamp(0.12rem, 0.1rem + 0.04vw, 0.2rem); + } + + .adv-megamenu__panel { + width: var(--adv-panel-width, min(1380px, calc(100vw - 1rem))); + min-width: var(--adv-panel-width, min(1380px, calc(100vw - 1rem))); + } + + .adv-megamenu__manual-tree li.has-submenu > a { + padding-bottom: clamp(0.45rem, 0.25rem + 0.25vw, 0.75rem); + padding-right: 1.25rem; + } + + .adv-megamenu .adv-megamenu__category-tree a, + .adv-megamenu .adv-megamenu__category-tree a:link, + .adv-megamenu .adv-megamenu__category-tree a:visited, + .adv-megamenu .adv-megamenu__manual-tree a, + .adv-megamenu .adv-megamenu__manual-tree a:link, + .adv-megamenu .adv-megamenu__manual-tree a:visited, + .adv-megamenu .adv-megamenu__panel-tree a, + .adv-megamenu .adv-megamenu__panel-tree a:link, + .adv-megamenu .adv-megamenu__panel-tree a:visited { + font-size: clamp(0.76rem, 0.56rem + 0.22vw, 0.95rem); + } + + .adv-megamenu__product-link { + padding: clamp(0.5rem, 0.3rem + 0.3vw, 0.75rem); + } + + .adv-megamenu__products { + gap: clamp(0.45rem, 0.2rem + 0.25vw, 0.75rem); + } + + .adv-megamenu__node-card .card-text { + display: none; + } + + .adv-megamenu__node-card { + border-radius: 10px; + } + + .adv-megamenu__rich-node-card { + gap: 0.8rem; + padding: 0.85rem; + } + + .adv-megamenu__rich-node-media { + flex-basis: 120px; + max-width: 120px; + } + + .adv-megamenu__rich-node-title { + font-size: 0.92rem; + } + + .adv-megamenu__rich-node-description { + font-size: 0.82rem; + } +} + +@media (min-width: 1600px) { + .adv-megamenu { + --adv-card-title-size: 1rem; + --adv-card-text-size: 0.92rem; + --adv-root-link-gap: 0.55rem; + --adv-root-link-min-height: 58px; + --adv-root-link-padding-x: 1rem; + --adv-root-font-size: 1rem; + --adv-panel-inner-gap: 2rem; + --adv-panel-inner-padding: 1.5rem; + --adv-panel-nav-min: 280px; + --adv-panel-nav-max: 320px; + --adv-submenu-title-size: 1.1rem; + --adv-rich-intro-gap: 1.25rem; + --adv-rich-intro-padding: 1.25rem; + --adv-rich-intro-media-width: 220px; + --adv-rich-intro-title-size: 1.35rem; + --adv-product-name-size: 0.9rem; + --adv-node-card-image-size: 150px; + --adv-node-card-body-padding: 0.55rem; + --adv-node-card-gap: 0.35rem; + } + + .adv-megamenu__node-card .card-text { + display: none; + } + + .adv-megamenu__rich-node-media { + flex-basis: 150px; + max-width: 150px; + } +} + +@media (max-width: 991px) { + .adv-megamenu__rich-node-card { + flex-direction: column; + } + + .adv-megamenu__rich-node-media { + flex-basis: auto; + max-width: 100%; + } +} diff --git a/views/js/admin.js b/views/js/admin.js index 23ccf09..5387f9a 100644 --- a/views/js/admin.js +++ b/views/js/admin.js @@ -1,421 +1,794 @@ (function () { function boot() { - var root = document.getElementById('adv-megamenu-admin'); - if (!root) { - return; - } - - var treeContainer = root.querySelector('.js-tree'); - var treeInput = document.getElementById('advmegamenu_tree_json'); - var ajaxUrl = root.getAttribute('data-ajax-url'); - var token = root.getAttribute('data-token'); - var languages = JSON.parse((root.querySelector('.js-adv-languages-data') || {}).textContent || '[]'); - var tree = JSON.parse((root.querySelector('.js-adv-tree-data') || {}).textContent || '[]'); - var tempId = Date.now(); - - function nextId() { - tempId += 1; - return tempId; - } - - function createEmptyNode() { - var lang = {}; - languages.forEach(function (language) { - lang[language.id_lang] = { - title: '', - description: '', - custom_link: '' - }; - }); - - return { - id: nextId(), - active: true, - type: 'category', - entity_id: 0, - icon_path: '', - icon_url: '', - new_window: false, - lang: lang, - layouts: [], - children: [] - }; - } - - function createEmptyLayout() { - return { - id: nextId(), - column_width: 4, - show_title: true, - custom_image: '', - custom_image_url: '', - background_color: '', - block_type: 'promo', - products: [] - }; - } - - function serializeTree() { - treeInput.value = JSON.stringify(tree); - } - - function render() { - serializeTree(); - treeContainer.innerHTML = ''; - tree.forEach(function (node, index) { - treeContainer.appendChild(renderNode(node, tree, index, 0)); - }); - } - - function move(collection, index, direction) { - var target = direction === 'up' ? index - 1 : index + 1; - if (target < 0 || target >= collection.length) { + var root = document.getElementById('adv-megamenu-admin'); + if (!root) { return; } - var current = collection[index]; - collection[index] = collection[target]; - collection[target] = current; - render(); - } + var treeContainer = root.querySelector('.js-tree'); + var treeInput = document.getElementById('advmegamenu_tree_json'); + var ajaxUrl = root.getAttribute('data-ajax-url'); + var token = root.getAttribute('data-token'); + var languages = parseJson(root.querySelector('.js-adv-languages-data'), []); + var labels = parseJson(root.querySelector('.js-adv-i18n'), {}); + var tree = parseJson(root.querySelector('.js-adv-tree-data'), []); + var tempId = Date.now(); + var nodeModal = document.getElementById('advNodeModal'); + var layoutModal = document.getElementById('advLayoutModal'); + var confirmModal = document.getElementById('advConfirmModal'); + var nodeModalState = null; + var layoutModalState = null; + var confirmAction = null; + var activeRootId = tree.length ? tree[0].id : null; - function remove(collection, index) { - collection.splice(index, 1); - render(); - } + function nextId() { + tempId += 1; + return tempId; + } - function renderNode(node, collection, index, depth) { - var wrapper = document.createElement('section'); - wrapper.className = 'adv-menu-node'; - - var title = getNodeTitle(node); - wrapper.innerHTML = '' + - '
' + - ' ' + escapeHtml(title || 'Menu item') + '' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
' + - '
' + - '
' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
'; - - wrapper.querySelector('.js-node-type').value = node.type || 'category'; - wrapper.querySelector('.js-entity-id').value = node.entity_id || 0; - wrapper.querySelector('.js-active').checked = !!node.active; - wrapper.querySelector('.js-new-window').checked = !!node.new_window; - wrapper.querySelector('.js-icon-path').value = node.icon_path || ''; - - var iconPreviewWrap = wrapper.querySelector('.js-icon-preview-wrap'); - renderImagePreview(iconPreviewWrap, node.icon_url); - - wrapper.querySelector('.js-node-type').addEventListener('change', function (event) { - node.type = event.target.value; - render(); - }); - wrapper.querySelector('.js-entity-id').addEventListener('input', function (event) { - node.entity_id = parseInt(event.target.value || '0', 10); - serializeTree(); - }); - wrapper.querySelector('.js-active').addEventListener('change', function (event) { - node.active = event.target.checked; - serializeTree(); - }); - wrapper.querySelector('.js-new-window').addEventListener('change', function (event) { - node.new_window = event.target.checked; - serializeTree(); - }); - - wrapper.querySelector('.js-icon-upload').addEventListener('change', function (event) { - uploadImage(event.target.files[0], 'menu_icon', function (response) { - node.icon_path = response.path; - node.icon_url = response.url; - serializeTree(); - renderImagePreview(iconPreviewWrap, response.url); - }); - }); - - wrapper.querySelector('.js-move-up').addEventListener('click', function () { - move(collection, index, 'up'); - }); - wrapper.querySelector('.js-move-down').addEventListener('click', function () { - move(collection, index, 'down'); - }); - wrapper.querySelector('.js-remove-node').addEventListener('click', function () { - remove(collection, index); - }); - wrapper.querySelector('.js-add-child').addEventListener('click', function () { - node.children = node.children || []; - node.children.push(createEmptyNode()); - render(); - }); - wrapper.querySelector('.js-add-layout').addEventListener('click', function () { - node.layouts = node.layouts || []; - node.layouts.push(createEmptyLayout()); - render(); - }); - - var langWrap = wrapper.querySelector('.adv-menu-node__languages'); - languages.forEach(function (language) { - var langId = String(language.id_lang); - if (!node.lang[langId]) { - node.lang[langId] = { title: '', description: '', custom_link: '' }; + function parseJson(element, fallback) { + if (!element || !element.textContent) { + return fallback; } - var box = document.createElement('div'); - box.className = 'adv-menu-language'; - box.innerHTML = '' + - '

' + escapeHtml(language.iso_code + ' / ' + language.name) + '

' + - '' + - '' + - ''; + try { + return JSON.parse(element.textContent); + } catch (error) { + return fallback; + } + } - box.querySelector('.js-lang-title').value = node.lang[langId].title || ''; - box.querySelector('.js-lang-description').value = node.lang[langId].description || ''; - box.querySelector('.js-lang-link').value = node.lang[langId].custom_link || ''; - box.querySelector('.js-lang-title').addEventListener('input', function (event) { - node.lang[langId].title = event.target.value; - serializeTree(); - }); - box.querySelector('.js-lang-description').addEventListener('input', function (event) { - node.lang[langId].description = event.target.value; - serializeTree(); - }); - box.querySelector('.js-lang-link').addEventListener('input', function (event) { - node.lang[langId].custom_link = event.target.value; - serializeTree(); + function t(key, fallback) { + return labels[key] || fallback || key; + } + + function showModal(element) { + if (window.jQuery && window.jQuery.fn && window.jQuery.fn.modal) { + window.jQuery(element).modal('show'); + } + } + + function hideModal(element) { + if (window.jQuery && window.jQuery.fn && window.jQuery.fn.modal) { + window.jQuery(element).modal('hide'); + } + } + + function createEmptyNode() { + var lang = {}; + languages.forEach(function (language) { + lang[String(language.id_lang)] = { + title: '', + description: '', + custom_link: '' + }; }); - langWrap.appendChild(box); - }); + return { + id: nextId(), + active: true, + type: 'link', + entity_id: 0, + icon_path: '', + icon_url: '', + new_window: false, + lang: lang, + layouts: [], + children: [] + }; + } - var layoutsWrap = wrapper.querySelector('.adv-menu-node__layouts'); - (node.layouts || []).forEach(function (layout, layoutIndex) { - layoutsWrap.appendChild(renderLayout(node.layouts, layout, layoutIndex)); - }); + function createEmptyLayout() { + return { + id: nextId(), + column_width: 4, + show_title: true, + custom_image: '', + custom_image_url: '', + background_color: '', + block_type: 'promo', + products: [] + }; + } - var childrenWrap = wrapper.querySelector('.adv-menu-node__children'); - (node.children || []).forEach(function (child, childIndex) { - childrenWrap.appendChild(renderNode(child, node.children, childIndex, depth + 1)); - }); - - return wrapper; - } - - function renderLayout(layouts, layout, index) { - var wrapper = document.createElement('section'); - wrapper.className = 'adv-menu-layout'; - wrapper.innerHTML = '' + - '
' + - ' Rich block' + - '
' + - ' ' + - ' ' + - ' ' + - '
' + - '
' + - '
' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
' + - '
' + - ' ' + - '
' + - '
' + - '
'; - - wrapper.querySelector('.js-layout-width').value = layout.column_width || 4; - wrapper.querySelector('.js-layout-type').value = layout.block_type || 'promo'; - wrapper.querySelector('.js-layout-title').checked = !!layout.show_title; - wrapper.querySelector('.js-layout-bg').value = layout.background_color || ''; - - var previewWrap = wrapper.querySelector('.js-layout-preview-wrap'); - renderImagePreview(previewWrap, layout.custom_image_url); - - wrapper.querySelector('.js-layout-width').addEventListener('input', function (event) { - layout.column_width = parseInt(event.target.value || '4', 10); - serializeTree(); - }); - wrapper.querySelector('.js-layout-type').addEventListener('change', function (event) { - layout.block_type = event.target.value; - serializeTree(); - }); - wrapper.querySelector('.js-layout-title').addEventListener('change', function (event) { - layout.show_title = event.target.checked; - serializeTree(); - }); - wrapper.querySelector('.js-layout-bg').addEventListener('input', function (event) { - layout.background_color = event.target.value; - serializeTree(); - }); - wrapper.querySelector('.js-layout-upload').addEventListener('change', function (event) { - uploadImage(event.target.files[0], 'layout_image', function (response) { - layout.custom_image = response.path; - layout.custom_image_url = response.url; - serializeTree(); - renderImagePreview(previewWrap, response.url); + function ensureNodeDefaults(node) { + if (!node.lang) { + node.lang = {}; + } + if (!node.layouts) { + node.layouts = []; + } + if (!node.children) { + node.children = []; + } + languages.forEach(function (language) { + var langId = String(language.id_lang); + if (!node.lang[langId]) { + node.lang[langId] = { + title: '', + description: '', + custom_link: '' + }; + } }); - }); + } - wrapper.querySelector('.js-layout-up').addEventListener('click', function () { - move(layouts, index, 'up'); - }); - wrapper.querySelector('.js-layout-down').addEventListener('click', function () { - move(layouts, index, 'down'); - }); - wrapper.querySelector('.js-remove-layout').addEventListener('click', function () { - remove(layouts, index); - }); + function sanitizeTree(nodes, depth) { + nodes.forEach(function (node) { + ensureNodeDefaults(node); - var productsWrap = wrapper.querySelector('.js-products'); - renderProducts(layout.products || [], productsWrap, function (nextProducts) { - layout.products = nextProducts; + if (depth === 0) { + node.icon_path = ''; + node.icon_url = ''; + Object.keys(node.lang).forEach(function (langId) { + node.lang[langId].description = ''; + }); + } + + sanitizeTree(node.children || [], depth + 1); + }); + } + + function serializeTree() { + sanitizeTree(tree, 0); + treeInput.value = JSON.stringify(tree); + } + + function render() { serializeTree(); - }); + treeContainer.innerHTML = ''; - var resultsWrap = wrapper.querySelector('.js-product-results'); - var searchInput = wrapper.querySelector('.js-product-search'); - var searchTimer = null; - searchInput.addEventListener('input', function () { - clearTimeout(searchTimer); - var value = searchInput.value.trim(); - if (value.length < 2) { - resultsWrap.innerHTML = ''; + if (!tree.length) { + var empty = document.createElement('div'); + empty.className = 'adv-megamenu-admin__empty'; + empty.textContent = t('emptyTree', 'No menu items yet.'); + treeContainer.appendChild(empty); return; } - searchTimer = window.setTimeout(function () { - fetch(ajaxUrl + '&ajax=1&action=searchProducts&q=' + encodeURIComponent(value)) - .then(function (response) { return response.json(); }) - .then(function (payload) { - resultsWrap.innerHTML = ''; - (payload.results || []).forEach(function (result) { - var button = document.createElement('button'); - button.type = 'button'; - button.textContent = result.label; - button.addEventListener('click', function () { - layout.products = layout.products || []; - if (layout.products.indexOf(result.id) === -1) { - layout.products.push(result.id); - } - render(); - }); - resultsWrap.appendChild(button); - }); - }); - }, 200); - }); + if (!findRootById(activeRootId)) { + activeRootId = tree[0].id; + } - return wrapper; - } + var shell = document.createElement('div'); + shell.className = 'adv-menu-admin-root-shell'; + shell.innerHTML = '' + + '
' + + '
'; - function renderProducts(products, wrap, onChange) { - wrap.innerHTML = ''; - products.forEach(function (productId, index) { - var chip = document.createElement('span'); - chip.className = 'adv-menu-product-chip'; - chip.innerHTML = '#' + escapeHtml(String(productId)) + ''; - chip.querySelector('button').addEventListener('click', function () { - var nextProducts = products.slice(); - nextProducts.splice(index, 1); - onChange(nextProducts); + var tabs = shell.querySelector('.adv-menu-admin-root-tabs'); + var panel = shell.querySelector('.adv-menu-admin-root-panel'); + + tree.forEach(function (node, index) { + tabs.appendChild(renderRootTab(node, tree, index)); + }); + + var activeRoot = findRootById(activeRootId) || tree[0]; + if (activeRoot) { + panel.appendChild(renderNode(activeRoot, tree, getRootIndex(activeRoot.id), 0)); + } + + treeContainer.appendChild(shell); + } + + function renderRootTab(node, collection, index) { + var button = document.createElement('button'); + button.type = 'button'; + button.className = 'adv-menu-admin-root-tab' + (node.id === activeRootId ? ' is-active' : ''); + button.innerHTML = '' + + '' + + ' ' + escapeHtml(getNodeTitle(node) || t('rootItem', 'Root item')) + '' + + ' ' + escapeHtml(node.type || 'link') + '' + + '' + + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + + button.addEventListener('click', function () { + activeRootId = node.id; render(); }); - wrap.appendChild(chip); - }); - } - function renderImagePreview(wrap, url) { - wrap.innerHTML = ''; - if (!url) { - return; + button.querySelector('.js-root-edit').addEventListener('click', function (event) { + event.stopPropagation(); + openNodeModal(node, 0); + }); + button.querySelector('.js-root-up').addEventListener('click', function (event) { + event.stopPropagation(); + move(collection, index, 'up'); + }); + button.querySelector('.js-root-down').addEventListener('click', function (event) { + event.stopPropagation(); + move(collection, index, 'down'); + }); + button.querySelector('.js-root-delete').addEventListener('click', function (event) { + event.stopPropagation(); + askConfirm(t('deleteNodeTitle', 'Delete menu item'), t('deleteNodeText', 'This will remove the selected menu item together with its children and attached blocks.'), function () { + collection.splice(index, 1); + if (activeRootId === node.id) { + activeRootId = collection.length ? collection[Math.max(0, index - 1)].id : null; + } + render(); + }); + }); + + return button; } - var image = document.createElement('img'); - image.src = url; - image.className = 'adv-menu-image-preview'; - wrap.appendChild(image); - } + function renderNode(node, collection, index, depth) { + ensureNodeDefaults(node); - function getNodeTitle(node) { - var firstLang = languages[0] ? String(languages[0].id_lang) : null; - if (firstLang && node.lang[firstLang] && node.lang[firstLang].title) { - return node.lang[firstLang].title; + var wrapper = document.createElement('section'); + wrapper.className = 'adv-menu-admin-node' + (depth === 0 ? ' adv-menu-admin-node--root' : '') + (depth >= 2 ? ' adv-menu-admin-node--compact' : ''); + + var title = getNodeTitle(node) || t(depth === 0 ? 'rootItem' : 'childItem', 'Menu item'); + var customLink = getNodeLink(node); + var imageHtml = depth > 0 && node.icon_url ? '' : ''; + var meta = []; + + meta.push(metaItem(node.type || 'link', 'icon-tag')); + if (!node.active) { + meta.push(metaItem(t('inactive', 'Inactive'), 'icon-ban-circle')); + } + if (node.new_window) { + meta.push(metaItem(t('newWindow', 'New window'), 'icon-external-link')); + } + meta.push(metaItem(String((node.children || []).length) + ' ' + t('children', 'children'), 'icon-sitemap')); + meta.push(metaItem(String((node.layouts || []).length) + ' ' + t('blocks', 'blocks'), 'icon-th-large')); + + wrapper.innerHTML = '' + + '
' + + '
' + + '

' + + ' ' + + ' ' + escapeHtml(title) + '' + + '

' + + '
' + meta.join('') + '
' + + '
' + + '
' + + actionButton('js-node-up', 'icon-arrow-up', '') + + actionButton('js-node-down', 'icon-arrow-down', '') + + actionButton('js-node-edit', 'icon-pencil', t('edit', 'Edit')) + + actionButton('js-node-add-child', 'icon-level-down', t('addChild', 'Add child')) + + actionButton('js-node-add-layout', 'icon-th-large', t('addBlock', 'Add block')) + + actionButton('js-node-delete btn-danger', 'icon-trash', t('delete', 'Delete')) + + '
' + + '
' + + '
' + + '
' + + '
' + + (customLink ? ' ' : '') + + (depth === 0 ? '

' + escapeHtml(t('rootRule', 'First level only stores title and custom link.')) + '

' : '') + + '
' + + '
' + imageHtml + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + + bindNodeActions(wrapper, node, collection, index, depth); + + var layoutsWrap = wrapper.querySelector('.adv-menu-admin-node__layouts-wrap'); + if ((node.layouts || []).length) { + var layoutsTitle = document.createElement('h5'); + layoutsTitle.className = 'adv-menu-admin-node__section-title'; + layoutsTitle.innerHTML = '' + escapeHtml(t('blocks', 'blocks')) + ''; + layoutsWrap.appendChild(layoutsTitle); + + var layoutList = document.createElement('div'); + layoutList.className = 'adv-menu-admin-layout-list'; + node.layouts.forEach(function (layout, layoutIndex) { + layoutList.appendChild(renderLayout(node.layouts, layout, layoutIndex)); + }); + layoutsWrap.appendChild(layoutList); + } + + var childrenWrap = wrapper.querySelector('.adv-menu-admin-node__children-wrap'); + if ((node.children || []).length) { + var childrenTitle = document.createElement('h5'); + childrenTitle.className = 'adv-menu-admin-node__section-title'; + childrenTitle.innerHTML = '' + escapeHtml(t('children', 'children')) + ''; + childrenWrap.appendChild(childrenTitle); + + var childList = document.createElement('div'); + childList.className = 'adv-menu-admin-children-list adv-menu-admin-node__children' + (depth >= 1 ? ' adv-menu-admin-node__children--grid' : ''); + node.children.forEach(function (child, childIndex) { + childList.appendChild(renderNode(child, node.children, childIndex, depth + 1)); + }); + childrenWrap.appendChild(childList); + } + + return wrapper; } - var langIds = Object.keys(node.lang || {}); - if (langIds.length && node.lang[langIds[0]]) { - return node.lang[langIds[0]].title || ''; + function renderLayout(layouts, layout, index) { + var wrapper = document.createElement('section'); + wrapper.className = 'adv-menu-admin-layout'; + + var meta = []; + meta.push(metaItem('col-' + (layout.column_width || 4), 'icon-columns')); + meta.push(metaItem(layout.block_type || 'promo', 'icon-th')); + if (layout.show_title) { + meta.push(metaItem(t('showTitle', 'Show title'), 'icon-font')); + } + if ((layout.products || []).length) { + meta.push(metaItem(String(layout.products.length) + ' ' + t('products', 'Products'), 'icon-shopping-cart')); + } + + wrapper.innerHTML = '' + + '
' + + '
' + + '
' + + ' ' + + ' ' + escapeHtml(t('block', 'Block')) + ' #' + escapeHtml(String(index + 1)) + '' + + '
' + + '
' + meta.join('') + '
' + + '
' + + '
' + + actionButton('js-layout-up', 'icon-arrow-up', '') + + actionButton('js-layout-down', 'icon-arrow-down', '') + + actionButton('js-layout-edit', 'icon-pencil', t('edit', 'Edit')) + + actionButton('js-layout-delete btn-danger', 'icon-trash', t('delete', 'Delete')) + + '
' + + '
' + + '
' + + (layout.custom_image_url ? '' : '') + + '
'; + + wrapper.querySelector('.js-layout-up').addEventListener('click', function () { + move(layouts, index, 'up'); + }); + wrapper.querySelector('.js-layout-down').addEventListener('click', function () { + move(layouts, index, 'down'); + }); + wrapper.querySelector('.js-layout-edit').addEventListener('click', function () { + openLayoutModal(layout); + }); + wrapper.querySelector('.js-layout-delete').addEventListener('click', function () { + askConfirm(t('deleteLayoutTitle', 'Delete rich block'), t('deleteLayoutText', 'This will remove the selected rich content block.'), function () { + layouts.splice(index, 1); + render(); + }); + }); + + return wrapper; } - return ''; - } - - function uploadImage(file, preset, callback) { - if (!file) { - return; + function bindNodeActions(wrapper, node, collection, index, depth) { + wrapper.querySelector('.js-node-up').addEventListener('click', function () { + move(collection, index, 'up'); + }); + wrapper.querySelector('.js-node-down').addEventListener('click', function () { + move(collection, index, 'down'); + }); + wrapper.querySelector('.js-node-edit').addEventListener('click', function () { + openNodeModal(node, depth); + }); + wrapper.querySelector('.js-node-add-child').addEventListener('click', function () { + var child = createEmptyNode(); + openNodeModal(child, depth + 1, { + onSave: function (savedChild) { + node.children.push(savedChild); + } + }); + }); + wrapper.querySelector('.js-node-add-layout').addEventListener('click', function () { + var layout = createEmptyLayout(); + openLayoutModal(layout, { + onSave: function (savedLayout) { + node.layouts.push(savedLayout); + } + }); + }); + wrapper.querySelector('.js-node-delete').addEventListener('click', function () { + askConfirm(t('deleteNodeTitle', 'Delete menu item'), t('deleteNodeText', 'This will remove the selected menu item together with its children and attached blocks.'), function () { + collection.splice(index, 1); + render(); + }); + }); } - var formData = new FormData(); - formData.append('ajax', '1'); - formData.append('action', 'uploadImage'); - formData.append('token', token); - formData.append('preset', preset || 'default'); - formData.append('image', file); + function actionButton(className, icon, label) { + return '' + + ''; + } - fetch(ajaxUrl, { - method: 'POST', - body: formData - }) - .then(function (response) { return response.json(); }) - .then(function (payload) { - if (payload && !payload.error) { - callback(payload); + function metaItem(text, icon) { + return '' + escapeHtml(text) + ''; + } + + function move(collection, index, direction) { + var target = direction === 'up' ? index - 1 : index + 1; + if (target < 0 || target >= collection.length) { + return; + } + + var current = collection[index]; + collection[index] = collection[target]; + collection[target] = current; + if (collection === tree && current.id === activeRootId) { + activeRootId = current.id; + } + render(); + } + + function findRootById(id) { + for (var i = 0; i < tree.length; i += 1) { + if (tree[i].id === id) { + return tree[i]; + } + } + + return null; + } + + function getRootIndex(id) { + for (var i = 0; i < tree.length; i += 1) { + if (tree[i].id === id) { + return i; + } + } + + return 0; + } + + function openNodeModal(node, depth, options) { + nodeModalState = { + node: node, + depth: depth, + onSave: options && typeof options.onSave === 'function' ? options.onSave : null + }; + ensureNodeDefaults(node); + + nodeModal.querySelector('.js-node-modal-title').textContent = t('nodeModalTitle', 'Edit menu item'); + nodeModal.querySelector('.js-node-type').value = node.type || 'link'; + nodeModal.querySelector('.js-node-entity-id').value = node.entity_id || 0; + nodeModal.querySelector('.js-node-active').checked = !!node.active; + nodeModal.querySelector('.js-node-new-window').checked = !!node.new_window; + + var rootHint = nodeModal.querySelector('.js-node-root-hint'); + var imageGroup = nodeModal.querySelector('.js-node-image-group'); + var previewWrap = nodeModal.querySelector('.js-node-icon-preview'); + renderImagePreview(previewWrap, depth > 0 ? node.icon_url : ''); + + rootHint.style.display = depth === 0 ? 'block' : 'none'; + rootHint.textContent = t('rootOnlyHint', 'For first-level items only title and custom link are edited here.'); + imageGroup.style.display = depth === 0 ? 'none' : 'block'; + + renderNodeLanguagePanels(node, depth); + + var uploadInput = nodeModal.querySelector('.js-node-icon-upload'); + uploadInput.value = ''; + uploadInput.onchange = function (event) { + uploadImage(event.target.files[0], 'menu_icon', function (response) { + node.icon_path = response.path; + node.icon_url = response.url; + renderImagePreview(previewWrap, response.url); + serializeTree(); + }); + }; + + showModal(nodeModal); + } + + function renderNodeLanguagePanels(node, depth) { + var wrap = nodeModal.querySelector('.js-node-languages'); + wrap.innerHTML = ''; + + languages.forEach(function (language) { + var langId = String(language.id_lang); + var lang = node.lang[langId] || { title: '', description: '', custom_link: '' }; + var panel = document.createElement('div'); + panel.className = 'adv-megamenu-admin__lang-panel'; + panel.innerHTML = '' + + '
' + escapeHtml(language.iso_code + ' / ' + language.name) + '
' + + '
' + + ' ' + + (depth === 0 ? '' : ( + ' ' + )) + + ' ' + + '
'; + + panel.querySelector('.js-lang-title').addEventListener('input', function (event) { + node.lang[langId].title = event.target.value; + serializeTree(); + }); + if (depth > 0) { + panel.querySelector('.js-lang-description').addEventListener('input', function (event) { + node.lang[langId].description = event.target.value; + serializeTree(); + }); + } + panel.querySelector('.js-lang-link').addEventListener('input', function (event) { + node.lang[langId].custom_link = event.target.value; + serializeTree(); + }); + + wrap.appendChild(panel); + }); + } + + function saveNodeModal() { + if (!nodeModalState) { + return; + } + + var node = nodeModalState.node; + var depth = nodeModalState.depth; + var onSave = nodeModalState.onSave; + node.type = nodeModal.querySelector('.js-node-type').value; + node.entity_id = parseInt(nodeModal.querySelector('.js-node-entity-id').value || '0', 10); + node.active = nodeModal.querySelector('.js-node-active').checked; + node.new_window = nodeModal.querySelector('.js-node-new-window').checked; + + if (depth === 0) { + node.icon_path = ''; + node.icon_url = ''; + Object.keys(node.lang).forEach(function (langId) { + node.lang[langId].description = ''; + }); + } + + if (onSave) { + onSave(node); + } + + hideModal(nodeModal); + nodeModalState = null; + render(); + } + + function openLayoutModal(layout, options) { + layoutModalState = { + layout: layout, + onSave: options && typeof options.onSave === 'function' ? options.onSave : null + }; + layout.products = layout.products || []; + + layoutModal.querySelector('.js-layout-width').value = layout.column_width || 4; + layoutModal.querySelector('.js-layout-type').value = layout.block_type || 'promo'; + layoutModal.querySelector('.js-layout-title').checked = !!layout.show_title; + layoutModal.querySelector('.js-layout-bg').value = layout.background_color || ''; + renderImagePreview(layoutModal.querySelector('.js-layout-preview'), layout.custom_image_url); + renderProducts(layout.products, layoutModal.querySelector('.js-layout-products')); + layoutModal.querySelector('.js-layout-product-results').innerHTML = ''; + layoutModal.querySelector('.js-layout-product-search').value = ''; + + var uploadInput = layoutModal.querySelector('.js-layout-upload'); + uploadInput.value = ''; + uploadInput.onchange = function (event) { + uploadImage(event.target.files[0], 'layout_image', function (response) { + layout.custom_image = response.path; + layout.custom_image_url = response.url; + renderImagePreview(layoutModal.querySelector('.js-layout-preview'), response.url); + serializeTree(); + }); + }; + + bindLayoutProductSearch(layout); + showModal(layoutModal); + } + + function bindLayoutProductSearch(layout) { + var resultsWrap = layoutModal.querySelector('.js-layout-product-results'); + var searchInput = layoutModal.querySelector('.js-layout-product-search'); + var searchTimer = null; + + searchInput.oninput = function () { + clearTimeout(searchTimer); + var value = searchInput.value.trim(); + if (value.length < 2) { + resultsWrap.innerHTML = ''; + return; + } + + searchTimer = window.setTimeout(function () { + fetch(ajaxUrl + '&ajax=1&action=searchProducts&q=' + encodeURIComponent(value)) + .then(function (response) { + return response.json(); + }) + .then(function (payload) { + resultsWrap.innerHTML = ''; + (payload.results || []).forEach(function (result) { + var button = document.createElement('button'); + button.type = 'button'; + button.textContent = result.label; + button.addEventListener('click', function () { + if (layout.products.indexOf(result.id) === -1) { + layout.products.push(result.id); + } + renderProducts(layout.products, layoutModal.querySelector('.js-layout-products')); + serializeTree(); + }); + resultsWrap.appendChild(button); + }); + }); + }, 200); + }; + } + + function renderProducts(products, wrap) { + wrap.innerHTML = ''; + if (!products.length) { + wrap.innerHTML = '' + escapeHtml(t('noProducts', 'No products attached.')) + ''; + return; + } + + products.forEach(function (productId, index) { + var chip = document.createElement('span'); + chip.className = 'adv-menu-product-chip'; + chip.innerHTML = '#' + escapeHtml(String(productId)) + ''; + chip.querySelector('button').addEventListener('click', function () { + products.splice(index, 1); + renderProducts(products, wrap); + serializeTree(); + }); + wrap.appendChild(chip); + }); + } + + function saveLayoutModal() { + if (!layoutModalState) { + return; + } + + var layout = layoutModalState.layout; + var onSave = layoutModalState.onSave; + layout.column_width = parseInt(layoutModal.querySelector('.js-layout-width').value || '4', 10); + layout.block_type = layoutModal.querySelector('.js-layout-type').value; + layout.show_title = layoutModal.querySelector('.js-layout-title').checked; + layout.background_color = layoutModal.querySelector('.js-layout-bg').value; + + if (onSave) { + onSave(layout); + } + + hideModal(layoutModal); + layoutModalState = null; + render(); + } + + function askConfirm(title, text, callback) { + confirmAction = callback; + confirmModal.querySelector('.js-confirm-title').textContent = title; + confirmModal.querySelector('.js-confirm-text').textContent = text; + showModal(confirmModal); + } + + function renderImagePreview(wrap, url) { + wrap.innerHTML = ''; + if (!url) { + return; + } + + var image = document.createElement('img'); + image.src = url; + image.className = 'adv-menu-image-preview'; + wrap.appendChild(image); + } + + function getNodeTitle(node) { + var defaultLang = languages[0] ? String(languages[0].id_lang) : null; + if (defaultLang && node.lang[defaultLang] && node.lang[defaultLang].title) { + return node.lang[defaultLang].title; + } + + var langIds = Object.keys(node.lang || {}); + for (var i = 0; i < langIds.length; i += 1) { + if (node.lang[langIds[i]] && node.lang[langIds[i]].title) { + return node.lang[langIds[i]].title; + } + } + + return ''; + } + + function getNodeLink(node) { + var defaultLang = languages[0] ? String(languages[0].id_lang) : null; + if (defaultLang && node.lang[defaultLang] && node.lang[defaultLang].custom_link) { + return node.lang[defaultLang].custom_link; + } + + var langIds = Object.keys(node.lang || {}); + for (var i = 0; i < langIds.length; i += 1) { + if (node.lang[langIds[i]] && node.lang[langIds[i]].custom_link) { + return node.lang[langIds[i]].custom_link; + } + } + + return ''; + } + + function uploadImage(file, preset, callback) { + if (!file) { + return; + } + + var formData = new FormData(); + formData.append('ajax', '1'); + formData.append('action', 'uploadImage'); + formData.append('token', token); + formData.append('preset', preset || 'default'); + formData.append('image', file); + + fetch(ajaxUrl, { + method: 'POST', + body: formData + }) + .then(function (response) { + return response.json(); + }) + .then(function (payload) { + if (payload && !payload.error) { + callback(payload); + } + }); + } + + function escapeHtml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function escapeAttribute(value) { + return escapeHtml(value).replace(/\n/g, ' '); + } + + root.querySelector('.js-add-root-node').addEventListener('click', function () { + var node = createEmptyNode(); + openNodeModal(node, 0, { + onSave: function (savedNode) { + tree.push(savedNode); + activeRootId = savedNode.id; } }); - } - - function escapeHtml(value) { - return value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - var addRootButton = root.querySelector('.js-add-root-node'); - if (addRootButton) { - addRootButton.addEventListener('click', function () { - tree.push(createEmptyNode()); - render(); }); - } - render(); + nodeModal.querySelector('.js-save-node').addEventListener('click', saveNodeModal); + layoutModal.querySelector('.js-save-layout').addEventListener('click', saveLayoutModal); + confirmModal.querySelector('.js-confirm-action').addEventListener('click', function () { + if (typeof confirmAction === 'function') { + confirmAction(); + } + hideModal(confirmModal); + confirmAction = null; + }); + + if (window.jQuery) { + window.jQuery(nodeModal).on('hidden.bs.modal', function () { + nodeModalState = null; + }); + window.jQuery(layoutModal).on('hidden.bs.modal', function () { + layoutModalState = null; + }); + window.jQuery(confirmModal).on('hidden.bs.modal', function () { + confirmAction = null; + }); + } + + render(); } if (document.readyState === 'loading') { diff --git a/views/js/megamenu.js b/views/js/megamenu.js index ecc99f4..68d0412 100644 --- a/views/js/megamenu.js +++ b/views/js/megamenu.js @@ -163,7 +163,7 @@ window.clearTimeout(hoverTimers.get(item)); var timer = window.setTimeout(function () { - closeUnlockedPanels(item); + closeAllPanels(item); positionBridgeForItem(item); activateFirstSubmenuPane(item); item.classList.add('is-open'); diff --git a/views/templates/admin/configure.tpl b/views/templates/admin/configure.tpl index dc0276f..f83af70 100644 --- a/views/templates/admin/configure.tpl +++ b/views/templates/admin/configure.tpl @@ -7,6 +7,55 @@ > +
@@ -16,9 +65,14 @@ {l s='Mega menu builder' d='Modules.Advancedmegamenu.Admin'} -

- {l s='Build the navigation tree, attach promo blocks and products, then save to regenerate the sprite and front cache.' d='Modules.Advancedmegamenu.Admin'} -

+
+

+ {l s='Manage the menu like on the storefront: quick actions on cards, editing in Bootstrap modals, and automatic sprite/front cache regeneration on save.' d='Modules.Advancedmegamenu.Admin'} +

+

+ {l s='First level only stores title and custom link. Description and item image are disabled there by design.' d='Modules.Advancedmegamenu.Admin'} +

+
+ + + + + + diff --git a/views/templates/hook/megamenu.tpl b/views/templates/hook/megamenu.tpl index 978ec3c..220310d 100644 --- a/views/templates/hook/megamenu.tpl +++ b/views/templates/hook/megamenu.tpl @@ -32,7 +32,7 @@ {if $nodes|count}
{foreach from=$nodes item=treeNode} -
+
{if $treeNode.icon_url} {$treeNode.title|escape:'htmlall':'UTF-8'} @@ -64,6 +64,35 @@ {/if} {/function} +{function name=advRichContentCards nodes=[]} + {if $nodes|count} +
+ {foreach from=$nodes item=treeNode} + + {/foreach} +
+ {/if} +{/function} + {function name=advRichContentIntro node=[]} {if $node.type == 'rich_content'}
@@ -132,7 +161,7 @@ {/if} {/function} -{function name=advMobilePanels nodes=[]} +{function name=advMobilePanels nodes=[] depth=0} {foreach from=$nodes item=node} {assign var=panelId value="adv-panel-"|cat:$node.id} {if $node.children|count || $node.category_branch|count || $node.layouts|count || $node.type == 'rich_content'} @@ -151,7 +180,12 @@ {/foreach} {foreach from=$node.children item=child}
  • -