@o2vend/theme-cli 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -0
- package/assets/Logo_o2vend.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-white.png +0 -0
- package/bin/o2vend +42 -0
- package/config/widget-map.json +50 -0
- package/lib/commands/check.js +201 -0
- package/lib/commands/generate.js +33 -0
- package/lib/commands/init.js +214 -0
- package/lib/commands/optimize.js +216 -0
- package/lib/commands/package.js +208 -0
- package/lib/commands/serve.js +105 -0
- package/lib/commands/validate.js +191 -0
- package/lib/lib/api-client.js +357 -0
- package/lib/lib/dev-server.js +2618 -0
- package/lib/lib/file-watcher.js +80 -0
- package/lib/lib/hot-reload.js +106 -0
- package/lib/lib/liquid-engine.js +822 -0
- package/lib/lib/liquid-filters.js +671 -0
- package/lib/lib/mock-api-server.js +989 -0
- package/lib/lib/mock-data.js +1468 -0
- package/lib/lib/widget-service.js +321 -0
- package/package.json +70 -0
- package/test-theme/README.md +27 -0
- package/test-theme/assets/async-sections.js +446 -0
- package/test-theme/assets/cart-drawer.js +463 -0
- package/test-theme/assets/cart-manager.js +223 -0
- package/test-theme/assets/checkout-price-handler.js +368 -0
- package/test-theme/assets/components.css +4629 -0
- package/test-theme/assets/delivery-zone.css +299 -0
- package/test-theme/assets/delivery-zone.js +396 -0
- package/test-theme/assets/logo.png +0 -0
- package/test-theme/assets/sections.css +48 -0
- package/test-theme/assets/theme.css +3500 -0
- package/test-theme/assets/theme.js +3745 -0
- package/test-theme/config/settings_data.json +292 -0
- package/test-theme/config/settings_schema.json +1050 -0
- package/test-theme/layout/theme.liquid +195 -0
- package/test-theme/locales/en.default.json +260 -0
- package/test-theme/sections/content-fallback.liquid +53 -0
- package/test-theme/sections/content.liquid +57 -0
- package/test-theme/sections/footer-fallback.liquid +328 -0
- package/test-theme/sections/footer.liquid +278 -0
- package/test-theme/sections/header-fallback.liquid +1805 -0
- package/test-theme/sections/header.liquid +1145 -0
- package/test-theme/sections/hero-fallback.liquid +212 -0
- package/test-theme/sections/hero.liquid +136 -0
- package/test-theme/snippets/account-sidebar.liquid +200 -0
- package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
- package/test-theme/snippets/breadcrumbs.liquid +134 -0
- package/test-theme/snippets/cart-drawer.liquid +467 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
- package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
- package/test-theme/snippets/delivery-zone-search.liquid +78 -0
- package/test-theme/snippets/icon.liquid +105 -0
- package/test-theme/snippets/login-modal.liquid +346 -0
- package/test-theme/snippets/mega-menu.liquid +812 -0
- package/test-theme/snippets/news-thumbnail.liquid +187 -0
- package/test-theme/snippets/pagination.liquid +120 -0
- package/test-theme/snippets/price.liquid +92 -0
- package/test-theme/snippets/product-card-related.liquid +78 -0
- package/test-theme/snippets/product-card-simple.liquid +41 -0
- package/test-theme/snippets/product-card.liquid +697 -0
- package/test-theme/snippets/rating.liquid +85 -0
- package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
- package/test-theme/snippets/skeleton-product-card.liquid +124 -0
- package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
- package/test-theme/snippets/social-sharing.liquid +185 -0
- package/test-theme/templates/account/dashboard.liquid +401 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
- package/test-theme/templates/account/loyalty.liquid +588 -0
- package/test-theme/templates/account/order-detail.liquid +230 -0
- package/test-theme/templates/account/orders.liquid +349 -0
- package/test-theme/templates/account/profile.liquid +758 -0
- package/test-theme/templates/account/register.liquid +232 -0
- package/test-theme/templates/account/return-orders.liquid +348 -0
- package/test-theme/templates/account/store-credit.liquid +464 -0
- package/test-theme/templates/account/subscriptions.liquid +601 -0
- package/test-theme/templates/account/wishlist.liquid +419 -0
- package/test-theme/templates/address-book.liquid +1092 -0
- package/test-theme/templates/categories.liquid +452 -0
- package/test-theme/templates/checkout.liquid +4511 -0
- package/test-theme/templates/error.liquid +384 -0
- package/test-theme/templates/index.liquid +11 -0
- package/test-theme/templates/login.liquid +185 -0
- package/test-theme/templates/order-confirmation.liquid +720 -0
- package/test-theme/templates/page.liquid +297 -0
- package/test-theme/templates/product-detail.liquid +4363 -0
- package/test-theme/templates/products.liquid +518 -0
- package/test-theme/templates/search.liquid +922 -0
- package/test-theme/theme.json.example +19 -0
- package/test-theme/widgets/brand-carousel.liquid +676 -0
- package/test-theme/widgets/brand.liquid +245 -0
- package/test-theme/widgets/carousel.liquid +843 -0
- package/test-theme/widgets/category-list-carousel.liquid +656 -0
- package/test-theme/widgets/category-list.liquid +340 -0
- package/test-theme/widgets/category.liquid +475 -0
- package/test-theme/widgets/discount-time.liquid +176 -0
- package/test-theme/widgets/footer-menu.liquid +695 -0
- package/test-theme/widgets/footer.liquid +179 -0
- package/test-theme/widgets/gallery.liquid +271 -0
- package/test-theme/widgets/header-menu.liquid +932 -0
- package/test-theme/widgets/header.liquid +159 -0
- package/test-theme/widgets/html.liquid +214 -0
- package/test-theme/widgets/news.liquid +217 -0
- package/test-theme/widgets/product-canvas.liquid +235 -0
- package/test-theme/widgets/product-carousel.liquid +502 -0
- package/test-theme/widgets/product.liquid +45 -0
- package/test-theme/widgets/recently-viewed.liquid +26 -0
- package/test-theme/widgets/shared/product-grid.liquid +339 -0
- package/test-theme/widgets/simple-product.liquid +42 -0
- package/test-theme/widgets/single-product.liquid +610 -0
- package/test-theme/widgets/spacebar-carousel.liquid +663 -0
- package/test-theme/widgets/spacebar.liquid +279 -0
- package/test-theme/widgets/splash.liquid +378 -0
- package/test-theme/widgets/testimonial-carousel.liquid +709 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
{% comment %}
|
|
2
|
+
Mega Menu Snippet
|
|
3
|
+
Displays a full-width mega menu with support for nested items
|
|
4
|
+
Parameters:
|
|
5
|
+
- menu: The menu object with items array (required)
|
|
6
|
+
Usage:
|
|
7
|
+
{% render 'snippets/mega-menu', menu: mainMenu %}
|
|
8
|
+
{% render 'snippets/mega-menu', menu: footerMenu %}
|
|
9
|
+
{% endcomment %}
|
|
10
|
+
{% comment %}
|
|
11
|
+
Header Menu Widget - FINAL FIX
|
|
12
|
+
Prevents duplicate menus and works on all pages
|
|
13
|
+
{% endcomment %}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
{% comment %}Desktop Navigation Menu{% endcomment %}
|
|
17
|
+
<nav class="header-menu-nav" data-header-menu-nav role="navigation" aria-label="Main navigation"
|
|
18
|
+
{% if menu and menu.items and menu.items.size > 0 %}data-server-menu="true"{% endif %}>
|
|
19
|
+
<div class="header-menu-nav__loading" data-menu-loading style="display: none;">
|
|
20
|
+
<span>Loading menu...</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="header-menu-nav__error" data-menu-error style="display: none;">
|
|
23
|
+
<span>Menu items unavailable</span>
|
|
24
|
+
</div>
|
|
25
|
+
<ul class="header-menu-nav__list" data-menu-list>
|
|
26
|
+
{% if menu and menu.items and menu.items.size > 0 %}
|
|
27
|
+
{% for item in menu.items %}
|
|
28
|
+
<li class="header-menu-nav__item {% if item.childItems and item.childItems.size > 0 %}has-children{% endif %}">
|
|
29
|
+
<a href="{{ item.link | default: '#' }}" class="header-menu-nav__link" aria-label="{{ item.name | default: item.title }}">{{ item.name | default: item.title }}</a>
|
|
30
|
+
{% if item.childItems and item.childItems.size > 0 %}
|
|
31
|
+
<ul class="header-menu-nav__submenu">
|
|
32
|
+
{% for child in item.childItems %}
|
|
33
|
+
<li class="header-menu-nav__submenu-item"><a href="{{ child.link | default: '#' }}" class="header-menu-nav__submenu-link">{{ child.name | default: child.title }}</a></li>
|
|
34
|
+
{% endfor %}
|
|
35
|
+
</ul>
|
|
36
|
+
{% endif %}
|
|
37
|
+
</li>
|
|
38
|
+
{% endfor %}
|
|
39
|
+
{% endif %}
|
|
40
|
+
</ul>
|
|
41
|
+
</nav>
|
|
42
|
+
|
|
43
|
+
{% comment %}Mobile Navigation Drawer{% endcomment %}
|
|
44
|
+
<div class="header-menu-nav__mobile-drawer" id="header-menu-mobile-drawer" aria-hidden="true">
|
|
45
|
+
<div class="header-menu-nav__mobile-drawer-content">
|
|
46
|
+
<div class="header-menu-nav__mobile-drawer-header">
|
|
47
|
+
<h2 class="header-menu-nav__mobile-drawer-title">Menu</h2>
|
|
48
|
+
<button type="button" class="header-menu-nav__mobile-drawer-close" aria-label="Close menu" data-header-menu-close>
|
|
49
|
+
<svg class="header-menu-nav__drawer-close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
50
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
51
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
<nav class="header-menu-nav__mobile-drawer-nav" role="navigation" aria-label="Mobile navigation">
|
|
56
|
+
<ul class="header-menu-nav__mobile-drawer-list" data-mobile-menu-list>
|
|
57
|
+
{% if menu and menu.items and menu.items.size > 0 %}
|
|
58
|
+
{% for item in menu.items %}
|
|
59
|
+
<li class="header-menu-nav__mobile-drawer-item {% if item.childItems and item.childItems.size > 0 %}has-children{% endif %}">
|
|
60
|
+
<a href="{{ item.link | default: '#' }}" class="header-menu-nav__mobile-drawer-link">{{ item.name | default: item.title }}</a>
|
|
61
|
+
{% if item.childItems and item.childItems.size > 0 %}
|
|
62
|
+
<ul class="header-menu-nav__mobile-drawer-sublist">
|
|
63
|
+
{% for child in item.childItems %}
|
|
64
|
+
<li class="header-menu-nav__mobile-drawer-subitem"><a href="{{ child.link | default: '#' }}" class="header-menu-nav__mobile-drawer-sublink">{{ child.name | default: child.title }}</a></li>
|
|
65
|
+
{% endfor %}
|
|
66
|
+
</ul>
|
|
67
|
+
{% endif %}
|
|
68
|
+
</li>
|
|
69
|
+
{% endfor %}
|
|
70
|
+
{% endif %}
|
|
71
|
+
</ul>
|
|
72
|
+
</nav>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
/* Menu Navigation Styles */
|
|
78
|
+
.header-menu-nav {
|
|
79
|
+
flex: 1 1 auto;
|
|
80
|
+
display: flex;
|
|
81
|
+
justify-content: flex-start;
|
|
82
|
+
align-items: center;
|
|
83
|
+
min-width: 0;
|
|
84
|
+
width: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@media (max-width: 768px) {
|
|
88
|
+
.header-menu-nav {
|
|
89
|
+
display: none !important;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.header-menu-nav__loading,
|
|
94
|
+
.header-menu-nav__error {
|
|
95
|
+
padding: 0.5rem 0;
|
|
96
|
+
color: #000;
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
opacity: 0.6;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.header-menu-nav__list {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 1.5rem;
|
|
105
|
+
list-style: none;
|
|
106
|
+
margin: 0;
|
|
107
|
+
padding: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.header-menu-nav__item {
|
|
111
|
+
position: relative;
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.header-menu-nav__link {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: var(--space-1, 0.25rem);
|
|
120
|
+
color: var(--menu-text, var(--header-text, #000));
|
|
121
|
+
text-decoration: none;
|
|
122
|
+
font-size: var(--text-base, 1rem);
|
|
123
|
+
font-weight: var(--font-weight-medium, 500);
|
|
124
|
+
line-height: var(--leading-normal, 1.5);
|
|
125
|
+
padding: var(--space-2, 0.5rem) 0;
|
|
126
|
+
transition: color var(--transition-fast, 150ms ease);
|
|
127
|
+
position: relative;
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.header-menu-nav__link:hover {
|
|
132
|
+
color: #333;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.header-menu-nav__item.has-children .header-menu-nav__link::after {
|
|
136
|
+
content: '';
|
|
137
|
+
display: inline-block;
|
|
138
|
+
width: 0;
|
|
139
|
+
height: 0;
|
|
140
|
+
border-left: 0.25rem solid transparent;
|
|
141
|
+
border-right: 0.25rem solid transparent;
|
|
142
|
+
border-top: 0.3125rem solid #000;
|
|
143
|
+
margin-left: 0.25rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.header-menu-nav__submenu {
|
|
147
|
+
position: absolute;
|
|
148
|
+
top: calc(100% + 0.5rem);
|
|
149
|
+
left: 0;
|
|
150
|
+
display: none;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
background: #fff;
|
|
153
|
+
padding: 0.5rem 0;
|
|
154
|
+
border-radius: 8px;
|
|
155
|
+
box-shadow: 0 1rem 1.5rem rgba(0, 0, 0, 0.1);
|
|
156
|
+
min-width: 12.5rem;
|
|
157
|
+
list-style: none;
|
|
158
|
+
margin: 0;
|
|
159
|
+
z-index: 100;
|
|
160
|
+
border: 0.125rem solid rgba(0, 0, 0, 0.08);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.header-menu-nav__item.has-children:hover .header-menu-nav__submenu {
|
|
164
|
+
display: flex;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.header-menu-nav__submenu-link {
|
|
168
|
+
display: block;
|
|
169
|
+
padding: 0.625rem 1.25rem;
|
|
170
|
+
color: #000;
|
|
171
|
+
text-decoration: none;
|
|
172
|
+
font-size: 0.875rem;
|
|
173
|
+
transition: background-color 150ms ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.header-menu-nav__submenu-link:hover {
|
|
177
|
+
background-color: rgba(0, 0, 0, 0.04);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Mobile Drawer - Only visible on mobile */
|
|
181
|
+
@media (max-width: 768px) {
|
|
182
|
+
/* Ensure drawer is visible even if parent is hidden - use fixed positioning to escape parent */
|
|
183
|
+
.site-header__bottom-row .header-menu-nav__mobile-drawer,
|
|
184
|
+
.header-menu-nav__mobile-drawer {
|
|
185
|
+
position: fixed !important;
|
|
186
|
+
top: 0 !important;
|
|
187
|
+
left: 0 !important;
|
|
188
|
+
right: 0 !important;
|
|
189
|
+
bottom: 0 !important;
|
|
190
|
+
width: 100% !important;
|
|
191
|
+
height: 100vh !important;
|
|
192
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
193
|
+
backdrop-filter: blur(2px);
|
|
194
|
+
z-index: 9999 !important;
|
|
195
|
+
pointer-events: none;
|
|
196
|
+
display: block !important;
|
|
197
|
+
opacity: 0;
|
|
198
|
+
visibility: hidden;
|
|
199
|
+
transition: opacity 300ms ease, visibility 300ms ease;
|
|
200
|
+
margin: 0 !important;
|
|
201
|
+
padding: 0 !important;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.site-header__bottom-row .header-menu-nav__mobile-drawer.active,
|
|
205
|
+
.header-menu-nav__mobile-drawer.active {
|
|
206
|
+
pointer-events: auto !important;
|
|
207
|
+
opacity: 1 !important;
|
|
208
|
+
visibility: visible !important;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.header-menu-nav__mobile-drawer.active {
|
|
212
|
+
pointer-events: auto;
|
|
213
|
+
opacity: 1 !important;
|
|
214
|
+
visibility: visible !important;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.header-menu-nav__mobile-drawer-content {
|
|
218
|
+
position: absolute !important;
|
|
219
|
+
top: 0 !important;
|
|
220
|
+
left: 0 !important;
|
|
221
|
+
width: 280px !important;
|
|
222
|
+
max-width: 85vw !important;
|
|
223
|
+
height: 100% !important;
|
|
224
|
+
background-color: #fff !important;
|
|
225
|
+
padding: 1.5rem !important;
|
|
226
|
+
overflow-y: auto !important;
|
|
227
|
+
transform: translateX(-100%);
|
|
228
|
+
transition: transform 300ms ease;
|
|
229
|
+
box-shadow: 0.125rem 0 0.5rem rgba(0, 0, 0, 0.15);
|
|
230
|
+
display: flex !important;
|
|
231
|
+
flex-direction: column !important;
|
|
232
|
+
margin: 0 !important;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.header-menu-nav__mobile-drawer.active .header-menu-nav__mobile-drawer-content {
|
|
236
|
+
transform: translateX(0) !important;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.header-menu-nav__mobile-drawer-header {
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
justify-content: space-between;
|
|
243
|
+
margin-bottom: 1.5rem;
|
|
244
|
+
padding-bottom: 0.75rem;
|
|
245
|
+
border-bottom: 0.125rem solid rgba(0, 0, 0, 0.08);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.header-menu-nav__mobile-drawer-title {
|
|
249
|
+
font-size: 1.125rem;
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
margin: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.header-menu-nav__mobile-drawer-close {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
width: 2.5rem;
|
|
259
|
+
height: 2.5rem;
|
|
260
|
+
background: none;
|
|
261
|
+
border: none;
|
|
262
|
+
cursor: pointer;
|
|
263
|
+
border-radius: 0.25rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.header-menu-nav__mobile-drawer-close:hover {
|
|
267
|
+
background-color: rgba(0, 0, 0, 0.04);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.header-menu-nav__drawer-close-icon {
|
|
271
|
+
width: 1.5rem;
|
|
272
|
+
height: 1.5rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.header-menu-nav__mobile-drawer-list {
|
|
276
|
+
list-style: none;
|
|
277
|
+
margin: 0;
|
|
278
|
+
padding: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.header-menu-nav__mobile-drawer-item {
|
|
282
|
+
border-bottom: 0.125rem solid rgba(0, 0, 0, 0.08);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.header-menu-nav__mobile-drawer-link {
|
|
286
|
+
display: block;
|
|
287
|
+
padding: 1rem 0;
|
|
288
|
+
color: #000;
|
|
289
|
+
text-decoration: none;
|
|
290
|
+
font-weight: 500;
|
|
291
|
+
font-size: 1.2rem;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.header-menu-nav__mobile-drawer-sublist {
|
|
295
|
+
list-style: none;
|
|
296
|
+
margin: 0;
|
|
297
|
+
padding: 0 0 0 1rem;
|
|
298
|
+
border-left: 0.125rem solid rgba(0, 0, 0, 0.08);
|
|
299
|
+
display: none;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.header-menu-nav__mobile-drawer-item.open .header-menu-nav__mobile-drawer-sublist {
|
|
303
|
+
display: block;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.header-menu-nav__mobile-drawer-sublink {
|
|
307
|
+
display: block;
|
|
308
|
+
padding: 0.75rem 0;
|
|
309
|
+
color: #000;
|
|
310
|
+
text-decoration: none;
|
|
311
|
+
font-size: 0.875rem;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Hide mobile drawer on desktop */
|
|
316
|
+
@media (min-width: 769px) {
|
|
317
|
+
.header-menu-nav__mobile-drawer {
|
|
318
|
+
display: none !important;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
</style>
|
|
322
|
+
|
|
323
|
+
<script>
|
|
324
|
+
(function() {
|
|
325
|
+
'use strict';
|
|
326
|
+
|
|
327
|
+
// Prevent multiple initializations
|
|
328
|
+
if (window.__headerMenuInitialized) {
|
|
329
|
+
console.log('[HeaderMenu] Already initialized, skipping');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
window.__headerMenuInitialized = true;
|
|
333
|
+
|
|
334
|
+
console.log('[HeaderMenu] Initializing (once)...');
|
|
335
|
+
|
|
336
|
+
function init() {
|
|
337
|
+
const menuNav = document.querySelector('[data-header-menu-nav]');
|
|
338
|
+
|
|
339
|
+
if (!menuNav) {
|
|
340
|
+
console.error('[HeaderMenu] Menu nav not found');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Move drawer to body level to avoid parent display:none issues
|
|
345
|
+
const drawer = document.getElementById('header-menu-mobile-drawer');
|
|
346
|
+
if (drawer && drawer.parentElement && drawer.parentElement !== document.body) {
|
|
347
|
+
console.log('[HeaderMenu] Moving drawer to body level');
|
|
348
|
+
document.body.appendChild(drawer);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const hasServerMenu = menuNav.getAttribute('data-server-menu') === 'true';
|
|
352
|
+
const menuList = menuNav.querySelector('[data-menu-list]');
|
|
353
|
+
const hasMenuItems = menuList && menuList.children.length > 0;
|
|
354
|
+
|
|
355
|
+
console.log('[HeaderMenu] Status:', {
|
|
356
|
+
hasServerMenu: hasServerMenu,
|
|
357
|
+
hasMenuItems: hasMenuItems,
|
|
358
|
+
itemCount: menuList ? menuList.children.length : 0
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Only fetch from API if NO menu items exist
|
|
362
|
+
if (!hasMenuItems) {
|
|
363
|
+
console.log('[HeaderMenu] No menu items found, loading from API...');
|
|
364
|
+
loadMainMenu(menuNav);
|
|
365
|
+
} else {
|
|
366
|
+
console.log('[HeaderMenu] Menu items already present, skipping API call');
|
|
367
|
+
setupSubmenuToggles();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Setup hamburger menu with retry (will find drawer internally)
|
|
371
|
+
setupHamburgerMenu();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function setupHamburgerMenu() {
|
|
375
|
+
let attempts = 0;
|
|
376
|
+
const maxAttempts = 15;
|
|
377
|
+
|
|
378
|
+
function trySetup() {
|
|
379
|
+
const toggle = document.querySelector('[data-header-menu-toggle]');
|
|
380
|
+
const drawer = document.getElementById('header-menu-mobile-drawer');
|
|
381
|
+
const drawerClose = document.querySelector('[data-header-menu-close]');
|
|
382
|
+
|
|
383
|
+
if (toggle && drawer) {
|
|
384
|
+
console.log('[HeaderMenu] Setting up hamburger menu', {
|
|
385
|
+
toggleFound: !!toggle,
|
|
386
|
+
drawerFound: !!drawer,
|
|
387
|
+
drawerCloseFound: !!drawerClose
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Use a flag to prevent duplicate listeners
|
|
391
|
+
if (toggle.dataset.menuListenerAttached === 'true') {
|
|
392
|
+
console.log('[HeaderMenu] Listener already attached, skipping');
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
toggle.dataset.menuListenerAttached = 'true';
|
|
396
|
+
|
|
397
|
+
// Toggle button click handler (only on mobile)
|
|
398
|
+
toggle.addEventListener('click', function(e) {
|
|
399
|
+
// Double check we're on mobile
|
|
400
|
+
if (window.innerWidth > 768) {
|
|
401
|
+
console.log('[HeaderMenu] Ignoring toggle click on desktop');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
e.preventDefault();
|
|
405
|
+
e.stopPropagation();
|
|
406
|
+
console.log('[HeaderMenu] Toggle clicked');
|
|
407
|
+
const currentDrawer = document.getElementById('header-menu-mobile-drawer');
|
|
408
|
+
if (!currentDrawer) {
|
|
409
|
+
console.error('[HeaderMenu] Drawer not found when toggling');
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const isOpen = currentDrawer.classList.contains('active');
|
|
413
|
+
console.log('[HeaderMenu] Drawer state:', isOpen ? 'open' : 'closed');
|
|
414
|
+
if (isOpen) {
|
|
415
|
+
closeDrawer(currentDrawer, toggle);
|
|
416
|
+
} else {
|
|
417
|
+
openDrawer(currentDrawer, toggle);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Close button handler
|
|
422
|
+
if (drawerClose) {
|
|
423
|
+
drawerClose.addEventListener('click', function(e) {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
e.stopPropagation();
|
|
426
|
+
console.log('[HeaderMenu] Close button clicked');
|
|
427
|
+
const currentDrawer = document.getElementById('header-menu-mobile-drawer');
|
|
428
|
+
if (currentDrawer) {
|
|
429
|
+
closeDrawer(currentDrawer, toggle);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Close on Escape key (only add once)
|
|
435
|
+
if (!window.__headerMenuEscapeHandler) {
|
|
436
|
+
window.__headerMenuEscapeHandler = function(e) {
|
|
437
|
+
if (e.key === 'Escape' && window.innerWidth <= 768) {
|
|
438
|
+
const currentDrawer = document.getElementById('header-menu-mobile-drawer');
|
|
439
|
+
if (currentDrawer && currentDrawer.classList.contains('active')) {
|
|
440
|
+
console.log('[HeaderMenu] Escape key pressed');
|
|
441
|
+
closeDrawer(currentDrawer, toggle);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
document.addEventListener('keydown', window.__headerMenuEscapeHandler);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Close on outside click (only add once)
|
|
449
|
+
if (!window.__headerMenuOverlayHandler) {
|
|
450
|
+
window.__headerMenuOverlayHandler = function(e) {
|
|
451
|
+
if (window.innerWidth > 768) return;
|
|
452
|
+
const currentDrawer = document.getElementById('header-menu-mobile-drawer');
|
|
453
|
+
if (currentDrawer && currentDrawer.classList.contains('active')) {
|
|
454
|
+
const drawerContent = currentDrawer.querySelector('.header-menu-nav__mobile-drawer-content');
|
|
455
|
+
// Close if clicking the backdrop (drawer itself) but not the content
|
|
456
|
+
if (e.target === currentDrawer || (drawerContent && !drawerContent.contains(e.target) && currentDrawer.contains(e.target))) {
|
|
457
|
+
console.log('[HeaderMenu] Backdrop clicked - closing drawer');
|
|
458
|
+
closeDrawer(currentDrawer, toggle);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
document.addEventListener('click', window.__headerMenuOverlayHandler);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log('[HeaderMenu] Hamburger menu setup complete');
|
|
466
|
+
return true;
|
|
467
|
+
} else {
|
|
468
|
+
attempts++;
|
|
469
|
+
if (attempts < maxAttempts) {
|
|
470
|
+
setTimeout(trySetup, 100);
|
|
471
|
+
} else {
|
|
472
|
+
console.warn('[HeaderMenu] Could not find hamburger menu elements after', maxAttempts, 'attempts', {
|
|
473
|
+
toggleFound: !!toggle,
|
|
474
|
+
drawerFound: !!drawer
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
trySetup();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function openDrawer(drawer, toggle) {
|
|
485
|
+
if (!drawer) {
|
|
486
|
+
console.error('[HeaderMenu] Cannot open drawer - drawer not found');
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// Only open on mobile
|
|
490
|
+
if (window.innerWidth > 768) {
|
|
491
|
+
console.log('[HeaderMenu] Ignoring open on desktop');
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
console.log('[HeaderMenu] Opening drawer', {
|
|
495
|
+
drawerElement: drawer,
|
|
496
|
+
drawerId: drawer.id,
|
|
497
|
+
hasActiveClass: drawer.classList.contains('active'),
|
|
498
|
+
windowWidth: window.innerWidth,
|
|
499
|
+
computedStyle: window.getComputedStyle(drawer).display,
|
|
500
|
+
zIndex: window.getComputedStyle(drawer).zIndex,
|
|
501
|
+
opacity: window.getComputedStyle(drawer).opacity,
|
|
502
|
+
visibility: window.getComputedStyle(drawer).visibility
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
drawer.classList.add('active');
|
|
506
|
+
drawer.setAttribute('aria-hidden', 'false');
|
|
507
|
+
|
|
508
|
+
// Force browser reflow to ensure styles are applied
|
|
509
|
+
void drawer.offsetHeight;
|
|
510
|
+
|
|
511
|
+
if (toggle) {
|
|
512
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
513
|
+
toggle.classList.add('is-open');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Prevent body scroll
|
|
517
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
518
|
+
document.body.style.paddingRight = scrollbarWidth + 'px';
|
|
519
|
+
document.body.style.overflow = 'hidden';
|
|
520
|
+
document.documentElement.style.overflow = 'hidden';
|
|
521
|
+
|
|
522
|
+
// Verify drawer is visible after opening
|
|
523
|
+
setTimeout(() => {
|
|
524
|
+
const computedStyle = window.getComputedStyle(drawer);
|
|
525
|
+
const content = drawer.querySelector('.header-menu-nav__mobile-drawer-content');
|
|
526
|
+
const contentStyle = content ? window.getComputedStyle(content) : null;
|
|
527
|
+
console.log('[HeaderMenu] Drawer state after opening:', {
|
|
528
|
+
hasActiveClass: drawer.classList.contains('active'),
|
|
529
|
+
display: computedStyle.display,
|
|
530
|
+
opacity: computedStyle.opacity,
|
|
531
|
+
visibility: computedStyle.visibility,
|
|
532
|
+
zIndex: computedStyle.zIndex,
|
|
533
|
+
contentTransform: contentStyle ? contentStyle.transform : 'N/A',
|
|
534
|
+
contentDisplay: contentStyle ? contentStyle.display : 'N/A'
|
|
535
|
+
});
|
|
536
|
+
}, 100);
|
|
537
|
+
|
|
538
|
+
console.log('[HeaderMenu] Drawer opened successfully');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function closeDrawer(drawer, toggle) {
|
|
542
|
+
if (!drawer) {
|
|
543
|
+
console.error('[HeaderMenu] Cannot close drawer - drawer not found');
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
console.log('[HeaderMenu] Closing drawer');
|
|
547
|
+
drawer.classList.remove('active');
|
|
548
|
+
drawer.setAttribute('aria-hidden', 'true');
|
|
549
|
+
if (toggle) {
|
|
550
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
551
|
+
toggle.classList.remove('is-open');
|
|
552
|
+
}
|
|
553
|
+
// Restore body scroll
|
|
554
|
+
document.body.style.paddingRight = '';
|
|
555
|
+
document.body.style.overflow = '';
|
|
556
|
+
document.documentElement.style.overflow = '';
|
|
557
|
+
console.log('[HeaderMenu] Drawer closed successfully');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function loadMainMenu(menuNav) {
|
|
561
|
+
const menuList = menuNav.querySelector('[data-menu-list]');
|
|
562
|
+
const loadingEl = menuNav.querySelector('[data-menu-loading]');
|
|
563
|
+
const errorEl = menuNav.querySelector('[data-menu-error]');
|
|
564
|
+
const mobileMenuList = document.querySelector('[data-mobile-menu-list]');
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
if (loadingEl) loadingEl.style.display = 'block';
|
|
568
|
+
if (errorEl) errorEl.style.display = 'none';
|
|
569
|
+
|
|
570
|
+
console.log('[HeaderMenu] Fetching menus from API...');
|
|
571
|
+
const menusResponse = await fetch('/webstoreapi/menus', {
|
|
572
|
+
headers: {
|
|
573
|
+
'Accept': 'application/json',
|
|
574
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
if (!menusResponse.ok) throw new Error('Failed to fetch menus: ' + menusResponse.status);
|
|
579
|
+
|
|
580
|
+
const menus = await menusResponse.json();
|
|
581
|
+
console.log('[HeaderMenu] Fetched menus:', menus);
|
|
582
|
+
|
|
583
|
+
if (!Array.isArray(menus) || menus.length === 0) {
|
|
584
|
+
throw new Error('No menus available');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Find main menu
|
|
588
|
+
let mainMenu = menus.find(m => (m.type || '').toLowerCase().trim() === 'main menu');
|
|
589
|
+
if (!mainMenu) {
|
|
590
|
+
console.log('[HeaderMenu] No "Main Menu" found, using first menu');
|
|
591
|
+
mainMenu = menus[0];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.log('[HeaderMenu] Selected menu:', mainMenu);
|
|
595
|
+
|
|
596
|
+
// Fetch menu details
|
|
597
|
+
const menuResponse = await fetch('/webstoreapi/menus/' + mainMenu.id, {
|
|
598
|
+
headers: {
|
|
599
|
+
'Accept': 'application/json',
|
|
600
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (!menuResponse.ok) throw new Error('Failed to fetch menu details: ' + menuResponse.status);
|
|
605
|
+
|
|
606
|
+
const menuData = await menuResponse.json();
|
|
607
|
+
console.log('[HeaderMenu] Menu data:', menuData);
|
|
608
|
+
|
|
609
|
+
if (!menuData.items || menuData.items.length === 0) {
|
|
610
|
+
throw new Error('No menu items');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
console.log('[HeaderMenu] Rendering', menuData.items.length, 'menu items');
|
|
614
|
+
renderMenuItems(menuList, menuData.items);
|
|
615
|
+
if (mobileMenuList) {
|
|
616
|
+
renderMobileMenuItems(mobileMenuList, menuData.items);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
setupSubmenuToggles();
|
|
620
|
+
|
|
621
|
+
if (loadingEl) loadingEl.style.display = 'none';
|
|
622
|
+
console.log('[HeaderMenu] Menu loaded successfully');
|
|
623
|
+
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.error('[HeaderMenu] Error loading menu:', error);
|
|
626
|
+
if (loadingEl) loadingEl.style.display = 'none';
|
|
627
|
+
if (errorEl) errorEl.style.display = 'block';
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Normalize menu link to ensure it's an absolute URL
|
|
633
|
+
* @param {string} link - Menu link from API
|
|
634
|
+
* @returns {string} Normalized absolute URL
|
|
635
|
+
*/
|
|
636
|
+
function normalizeMenuLink(link) {
|
|
637
|
+
if (!link || link === '#' || link.trim() === '') {
|
|
638
|
+
return '#';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const trimmedLink = link.trim();
|
|
642
|
+
|
|
643
|
+
// If already an absolute URL (http:// or https://), return as-is
|
|
644
|
+
if (trimmedLink.startsWith('http://') || trimmedLink.startsWith('https://')) {
|
|
645
|
+
return trimmedLink;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// If already starts with /, it's absolute from root, return as-is
|
|
649
|
+
if (trimmedLink.startsWith('/')) {
|
|
650
|
+
return trimmedLink;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Otherwise, prepend base URL to make it absolute
|
|
654
|
+
const baseUrl = window.location.origin;
|
|
655
|
+
// Remove leading slash from link if present, then add it back with baseUrl
|
|
656
|
+
const cleanLink = trimmedLink.startsWith('/') ? trimmedLink : '/' + trimmedLink;
|
|
657
|
+
return baseUrl + cleanLink;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function renderMenuItems(container, items) {
|
|
661
|
+
container.innerHTML = '';
|
|
662
|
+
items.forEach(item => {
|
|
663
|
+
const li = document.createElement('li');
|
|
664
|
+
li.className = 'header-menu-nav__item';
|
|
665
|
+
if (item.childItems && item.childItems.length > 0) {
|
|
666
|
+
li.classList.add('has-children');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const link = document.createElement('a');
|
|
670
|
+
link.href = normalizeMenuLink(item.link);
|
|
671
|
+
link.className = 'header-menu-nav__link';
|
|
672
|
+
link.textContent = item.name || item.title || '';
|
|
673
|
+
li.appendChild(link);
|
|
674
|
+
|
|
675
|
+
if (item.childItems && item.childItems.length > 0) {
|
|
676
|
+
const submenu = document.createElement('ul');
|
|
677
|
+
submenu.className = 'header-menu-nav__submenu';
|
|
678
|
+
item.childItems.forEach(child => {
|
|
679
|
+
const subLi = document.createElement('li');
|
|
680
|
+
subLi.className = 'header-menu-nav__submenu-item';
|
|
681
|
+
const subLink = document.createElement('a');
|
|
682
|
+
subLink.href = normalizeMenuLink(child.link);
|
|
683
|
+
subLink.className = 'header-menu-nav__submenu-link';
|
|
684
|
+
subLink.textContent = child.name || child.title || '';
|
|
685
|
+
subLi.appendChild(subLink);
|
|
686
|
+
submenu.appendChild(subLi);
|
|
687
|
+
});
|
|
688
|
+
li.appendChild(submenu);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
container.appendChild(li);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function renderMobileMenuItems(container, items) {
|
|
696
|
+
container.innerHTML = '';
|
|
697
|
+
items.forEach(item => {
|
|
698
|
+
const li = document.createElement('li');
|
|
699
|
+
li.className = 'header-menu-nav__mobile-drawer-item';
|
|
700
|
+
if (item.childItems && item.childItems.length > 0) {
|
|
701
|
+
li.classList.add('has-children');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const link = document.createElement('a');
|
|
705
|
+
link.href = normalizeMenuLink(item.link);
|
|
706
|
+
link.className = 'header-menu-nav__mobile-drawer-link';
|
|
707
|
+
link.textContent = item.name || item.title || '';
|
|
708
|
+
li.appendChild(link);
|
|
709
|
+
|
|
710
|
+
if (item.childItems && item.childItems.length > 0) {
|
|
711
|
+
const submenu = document.createElement('ul');
|
|
712
|
+
submenu.className = 'header-menu-nav__mobile-drawer-sublist';
|
|
713
|
+
item.childItems.forEach(child => {
|
|
714
|
+
const subLi = document.createElement('li');
|
|
715
|
+
subLi.className = 'header-menu-nav__mobile-drawer-subitem';
|
|
716
|
+
const subLink = document.createElement('a');
|
|
717
|
+
subLink.href = normalizeMenuLink(child.link);
|
|
718
|
+
subLink.className = 'header-menu-nav__mobile-drawer-sublink';
|
|
719
|
+
subLink.textContent = child.name || child.title || '';
|
|
720
|
+
subLi.appendChild(subLink);
|
|
721
|
+
submenu.appendChild(subLi);
|
|
722
|
+
});
|
|
723
|
+
li.appendChild(submenu);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
container.appendChild(li);
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function setupSubmenuToggles() {
|
|
731
|
+
const drawer = document.getElementById('header-menu-mobile-drawer');
|
|
732
|
+
const drawerContainer = drawer ? drawer.querySelector('[data-mobile-menu-list]') : null;
|
|
733
|
+
|
|
734
|
+
// Get items from drawer
|
|
735
|
+
const items = drawerContainer ? drawerContainer.querySelectorAll('.header-menu-nav__mobile-drawer-item.has-children') : [];
|
|
736
|
+
|
|
737
|
+
console.log('[HeaderMenu] Setting up submenu toggles, found', items.length, 'items with children');
|
|
738
|
+
|
|
739
|
+
items.forEach(item => {
|
|
740
|
+
const link = item.querySelector('.header-menu-nav__mobile-drawer-link');
|
|
741
|
+
if (link) {
|
|
742
|
+
// Remove existing listener if any
|
|
743
|
+
const newLink = link.cloneNode(true);
|
|
744
|
+
link.parentNode.replaceChild(newLink, link);
|
|
745
|
+
|
|
746
|
+
newLink.addEventListener('click', function(e) {
|
|
747
|
+
const submenu = item.querySelector('.header-menu-nav__mobile-drawer-sublist');
|
|
748
|
+
if (submenu) {
|
|
749
|
+
e.preventDefault();
|
|
750
|
+
e.stopPropagation();
|
|
751
|
+
const isOpen = item.classList.contains('open');
|
|
752
|
+
item.classList.toggle('open');
|
|
753
|
+
console.log('[HeaderMenu] Submenu toggled:', item.classList.contains('open') ? 'opened' : 'closed');
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Normalize menu links that were rendered server-side via Liquid
|
|
762
|
+
* This ensures links are absolute URLs even when rendered from templates
|
|
763
|
+
*/
|
|
764
|
+
function normalizeServerRenderedLinks() {
|
|
765
|
+
const normalizeLink = (link) => {
|
|
766
|
+
if (!link || link === '#' || link.trim() === '') {
|
|
767
|
+
return '#';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const trimmedLink = link.trim();
|
|
771
|
+
|
|
772
|
+
// If already an absolute URL (http:// or https://), return as-is
|
|
773
|
+
if (trimmedLink.startsWith('http://') || trimmedLink.startsWith('https://')) {
|
|
774
|
+
return trimmedLink;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// If already starts with /, it's absolute from root, return as-is
|
|
778
|
+
if (trimmedLink.startsWith('/')) {
|
|
779
|
+
return trimmedLink;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Otherwise, prepend base URL to make it absolute
|
|
783
|
+
const baseUrl = window.location.origin;
|
|
784
|
+
const cleanLink = trimmedLink.startsWith('/') ? trimmedLink : '/' + trimmedLink;
|
|
785
|
+
return baseUrl + cleanLink;
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// Find all menu links in the server-rendered menu
|
|
789
|
+
const menuLinks = document.querySelectorAll('[data-header-menu-nav] .header-menu-nav__link, [data-header-menu-nav] .header-menu-nav__submenu-link, [data-header-menu-nav] .header-menu-nav__mobile-drawer-link, [data-header-menu-nav] .header-menu-nav__mobile-drawer-sublink');
|
|
790
|
+
|
|
791
|
+
menuLinks.forEach(link => {
|
|
792
|
+
const currentHref = link.getAttribute('href');
|
|
793
|
+
if (currentHref && currentHref !== '#') {
|
|
794
|
+
const normalizedHref = normalizeLink(currentHref);
|
|
795
|
+
if (normalizedHref !== currentHref) {
|
|
796
|
+
link.setAttribute('href', normalizedHref);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (document.readyState === 'loading') {
|
|
803
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
804
|
+
init();
|
|
805
|
+
normalizeServerRenderedLinks();
|
|
806
|
+
});
|
|
807
|
+
} else {
|
|
808
|
+
init();
|
|
809
|
+
normalizeServerRenderedLinks();
|
|
810
|
+
}
|
|
811
|
+
})();
|
|
812
|
+
</script>
|