asjs-express 1.1.0 → 1.2.0
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 +150 -2
- package/lib/asjs.js +227 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# asjs-express
|
|
2
2
|
|
|
3
|
-
ASJS is a lightweight
|
|
3
|
+
ASJS is a lightweight view engine for Express, built for projects that want to keep server-rendered pages simple, maintainable, and pleasant to work on while still benefiting from layouts, async data preparation, partials, and modern navigation behavior.
|
|
4
4
|
|
|
5
|
-
Bu
|
|
5
|
+
Bu depodaki örnek yüzey WebAS adıyla anlatılıyor. Giriş tonu özellikle daha doğal tutuldu; sanki aynı projelerde uzun süredir birlikte çalışan küçük bir ekip kendi düzenini, çalışma biçimini ve arayüz yaklaşımını sakin bir dille anlatıyormuş gibi okunması istendi.
|
|
6
6
|
|
|
7
7
|
## English
|
|
8
8
|
|
|
@@ -97,6 +97,80 @@ app.listen(3000)
|
|
|
97
97
|
`asjs.clientTags()` injects the built-in ASJS stylesheet and router script for you.
|
|
98
98
|
`theme: true` optionally loads the packaged light, corporate WebAS theme.
|
|
99
99
|
|
|
100
|
+
### Built-in SPA header
|
|
101
|
+
|
|
102
|
+
ASJS now ships with a built-in header helper for the default SPA flow.
|
|
103
|
+
You do not have to define `brand`, `siteCta`, or `isActiveNavItem` just to make a working header. If you want a standard header, `navItems` is the only thing you will usually customize.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const express = require('express')
|
|
107
|
+
const { setupAsjs } = require('asjs-express')
|
|
108
|
+
|
|
109
|
+
const app = express()
|
|
110
|
+
|
|
111
|
+
const asjs = setupAsjs(app, {
|
|
112
|
+
rootDir: __dirname,
|
|
113
|
+
defaultLayout: 'layouts/main',
|
|
114
|
+
navItems: [
|
|
115
|
+
{ href: '/', label: 'Home', activeMode: 'exact' },
|
|
116
|
+
{ href: '/products', label: 'Products', activeMode: 'exact' },
|
|
117
|
+
{ href: '/contact', label: 'Contact', activeMode: 'exact' }
|
|
118
|
+
],
|
|
119
|
+
transitions: 'fade',
|
|
120
|
+
prefetch: true,
|
|
121
|
+
loadingBar: true
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```asjs
|
|
126
|
+
<!DOCTYPE html>
|
|
127
|
+
<html>
|
|
128
|
+
<head>
|
|
129
|
+
<meta charset="UTF-8">
|
|
130
|
+
<title><%= title %></title>
|
|
131
|
+
<%- asjs.clientTags({ preload: true, theme: true }) %>
|
|
132
|
+
</head>
|
|
133
|
+
<body<%- asjs.bodyAttrs() %>>
|
|
134
|
+
<%- asjs.progressMarkup() %>
|
|
135
|
+
<%- asjs.header() %>
|
|
136
|
+
<main<%- asjs.viewAttrs() %>>
|
|
137
|
+
<%- body %>
|
|
138
|
+
</main>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The built-in header is SPA-aware by default.
|
|
144
|
+
It renders `data-asjs-link`, uses the current request path automatically, and applies active states internally.
|
|
145
|
+
|
|
146
|
+
If you do not define a brand, ASJS generates a safe default brand object.
|
|
147
|
+
If you do not define `siteCta`, the header still works and simply renders without the CTA button.
|
|
148
|
+
If you want to customize them later, you can still pass them through `setupAsjs()`.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const asjs = setupAsjs(app, {
|
|
152
|
+
rootDir: __dirname,
|
|
153
|
+
defaultLayout: 'layouts/main',
|
|
154
|
+
navItems: [
|
|
155
|
+
{ href: '/', label: 'Overview' },
|
|
156
|
+
{ href: '/contact', label: 'Contact' }
|
|
157
|
+
],
|
|
158
|
+
brand: {
|
|
159
|
+
href: '/',
|
|
160
|
+
mark: 'WA',
|
|
161
|
+
name: 'WebAS',
|
|
162
|
+
tagline: 'Shared interface structure for Express projects'
|
|
163
|
+
},
|
|
164
|
+
siteCta: {
|
|
165
|
+
href: '/contact',
|
|
166
|
+
label: 'Start a project',
|
|
167
|
+
transition: 'slide'
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If you still prefer a custom partial, ASJS now also injects `brand`, `siteCta`, `currentPath`, and `isActiveNavItem` into the view locals automatically.
|
|
173
|
+
|
|
100
174
|
### Work before the page loads
|
|
101
175
|
|
|
102
176
|
Yes. If you pass an async callback to `asjs.page()`, ASJS waits for that work to finish and only then calls `res.render(viewName, locals)`.
|
|
@@ -537,6 +611,80 @@ app.listen(3000)
|
|
|
537
611
|
`asjs.clientTags()` çağrısı dahili ASJS stil ve router etiketlerini otomatik üretir.
|
|
538
612
|
`theme: true` ise açık renkli, kurumsal WebAS temasını yükler.
|
|
539
613
|
|
|
614
|
+
### Dahili SPA header sistemi
|
|
615
|
+
|
|
616
|
+
ASJS artık varsayılan SPA akışı için dahili bir header helper ile geliyor.
|
|
617
|
+
Çalışan bir header kurmak için `brand`, `siteCta` veya `isActiveNavItem` tanımlamak zorunda değilsin. Standart bir header istiyorsan çoğu durumda sadece `navItems` alanını düzenlemen yeterlidir.
|
|
618
|
+
|
|
619
|
+
```js
|
|
620
|
+
const express = require('express')
|
|
621
|
+
const { setupAsjs } = require('asjs-express')
|
|
622
|
+
|
|
623
|
+
const app = express()
|
|
624
|
+
|
|
625
|
+
const asjs = setupAsjs(app, {
|
|
626
|
+
rootDir: __dirname,
|
|
627
|
+
defaultLayout: 'layouts/main',
|
|
628
|
+
navItems: [
|
|
629
|
+
{ href: '/', label: 'Ana Sayfa', activeMode: 'exact' },
|
|
630
|
+
{ href: '/products', label: 'Ürünler', activeMode: 'exact' },
|
|
631
|
+
{ href: '/contact', label: 'İletişim', activeMode: 'exact' }
|
|
632
|
+
],
|
|
633
|
+
transitions: 'fade',
|
|
634
|
+
prefetch: true,
|
|
635
|
+
loadingBar: true
|
|
636
|
+
})
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
```asjs
|
|
640
|
+
<!DOCTYPE html>
|
|
641
|
+
<html>
|
|
642
|
+
<head>
|
|
643
|
+
<meta charset="UTF-8">
|
|
644
|
+
<title><%= title %></title>
|
|
645
|
+
<%- asjs.clientTags({ preload: true, theme: true }) %>
|
|
646
|
+
</head>
|
|
647
|
+
<body<%- asjs.bodyAttrs() %>>
|
|
648
|
+
<%- asjs.progressMarkup() %>
|
|
649
|
+
<%- asjs.header() %>
|
|
650
|
+
<main<%- asjs.viewAttrs() %>>
|
|
651
|
+
<%- body %>
|
|
652
|
+
</main>
|
|
653
|
+
</body>
|
|
654
|
+
</html>
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Bu dahili header varsayılan olarak SPA uyumludur.
|
|
658
|
+
Linkleri `data-asjs-link` ile üretir, mevcut request path bilgisini kendi alır ve aktif link durumunu içeride hesaplar.
|
|
659
|
+
|
|
660
|
+
`brand` tanımlamazsan ASJS güvenli bir varsayılan brand nesnesi üretir.
|
|
661
|
+
`siteCta` tanımlamazsan header bozulmaz; sadece sağ taraftaki CTA düğmesi çizilmez.
|
|
662
|
+
Daha sonra özelleştirmek istersen bunları yine `setupAsjs()` üzerinden verebilirsin.
|
|
663
|
+
|
|
664
|
+
```js
|
|
665
|
+
const asjs = setupAsjs(app, {
|
|
666
|
+
rootDir: __dirname,
|
|
667
|
+
defaultLayout: 'layouts/main',
|
|
668
|
+
navItems: [
|
|
669
|
+
{ href: '/', label: 'Genel Bakış' },
|
|
670
|
+
{ href: '/contact', label: 'İletişim' }
|
|
671
|
+
],
|
|
672
|
+
brand: {
|
|
673
|
+
href: '/',
|
|
674
|
+
mark: 'WA',
|
|
675
|
+
name: 'WebAS',
|
|
676
|
+
tagline: 'Express projeleri için ortak arayüz yapısı'
|
|
677
|
+
},
|
|
678
|
+
siteCta: {
|
|
679
|
+
href: '/contact',
|
|
680
|
+
label: 'Proje başlat',
|
|
681
|
+
transition: 'slide'
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Kendi partial yapını yazmak istersen ASJS artık `brand`, `siteCta`, `currentPath` ve `isActiveNavItem` alanlarını da view locals içine otomatik koyar.
|
|
687
|
+
|
|
540
688
|
### Sayfa yüklenmeden önce işlem yapmak
|
|
541
689
|
|
|
542
690
|
Evet, mümkün. `asjs.page()` içine async bir callback verirsen ASJS bu işlemin bitmesini bekler, ardından `res.render(viewName, locals)` çağrısını yapar.
|
package/lib/asjs.js
CHANGED
|
@@ -184,6 +184,196 @@ function renderClientTags(assets, options = {}) {
|
|
|
184
184
|
return tags.join('\n');
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
function normalizePathname(value) {
|
|
188
|
+
const normalized = String(value || '/').replace(/\/+$/, '');
|
|
189
|
+
return normalized || '/';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isActiveNavItem(currentPath, item = {}) {
|
|
193
|
+
const pathname = normalizePathname(currentPath);
|
|
194
|
+
const targetPath = normalizePathname(item.matchPath || item.href || '/');
|
|
195
|
+
const activeMode = String(item.activeMode || 'exact').toLowerCase();
|
|
196
|
+
|
|
197
|
+
if (activeMode === 'prefix') {
|
|
198
|
+
if (targetPath === '/') {
|
|
199
|
+
return pathname === '/';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return pathname === targetPath || pathname.startsWith(`${targetPath}/`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return pathname === targetPath;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeNavItems(value) {
|
|
209
|
+
if (!Array.isArray(value)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return value
|
|
214
|
+
.filter((item) => item && typeof item === 'object')
|
|
215
|
+
.map((item) => ({
|
|
216
|
+
...item,
|
|
217
|
+
href: String(item.href || '/'),
|
|
218
|
+
label: item.label ? String(item.label) : String(item.href || '/'),
|
|
219
|
+
activeMode: String(item.activeMode || 'exact').toLowerCase()
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function normalizeBrandOptions(value, fallback = {}) {
|
|
224
|
+
const defaults = {
|
|
225
|
+
href: '/',
|
|
226
|
+
mark: 'AS',
|
|
227
|
+
name: fallback.name || 'ASJS',
|
|
228
|
+
tagline: fallback.tagline || 'Server-rendered interface shell for Express',
|
|
229
|
+
caption: fallback.caption || '',
|
|
230
|
+
transition: 'fade'
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (typeof value === 'string' && value) {
|
|
234
|
+
return {
|
|
235
|
+
...defaults,
|
|
236
|
+
name: value
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (value && typeof value === 'object') {
|
|
241
|
+
return {
|
|
242
|
+
...defaults,
|
|
243
|
+
...value,
|
|
244
|
+
href: String(value.href || defaults.href),
|
|
245
|
+
mark: value.mark ? String(value.mark) : defaults.mark,
|
|
246
|
+
name: value.name ? String(value.name) : defaults.name,
|
|
247
|
+
tagline: value.tagline ? String(value.tagline) : defaults.tagline,
|
|
248
|
+
caption: value.caption ? String(value.caption) : defaults.caption,
|
|
249
|
+
transition: value.transition ? String(value.transition) : defaults.transition
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return defaults;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeSiteCtaOptions(value, fallback = null) {
|
|
257
|
+
const defaults = fallback && typeof fallback === 'object'
|
|
258
|
+
? {
|
|
259
|
+
href: String(fallback.href || '/'),
|
|
260
|
+
label: fallback.label ? String(fallback.label) : 'Get Started',
|
|
261
|
+
transition: fallback.transition ? String(fallback.transition) : 'fade'
|
|
262
|
+
}
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
if (value === undefined) {
|
|
266
|
+
return defaults;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (value === false || value === null) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (typeof value === 'string' && value) {
|
|
274
|
+
return {
|
|
275
|
+
href: value,
|
|
276
|
+
label: defaults ? defaults.label : 'Get Started',
|
|
277
|
+
transition: defaults ? defaults.transition : 'fade'
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (value && typeof value === 'object') {
|
|
282
|
+
return {
|
|
283
|
+
href: String(value.href || (defaults ? defaults.href : '/')),
|
|
284
|
+
label: value.label ? String(value.label) : (defaults ? defaults.label : 'Get Started'),
|
|
285
|
+
transition: value.transition ? String(value.transition) : (defaults ? defaults.transition : 'fade')
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return defaults;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function resolveHeaderState(config, sourceLocals = {}, options = {}) {
|
|
293
|
+
const locals = sourceLocals && typeof sourceLocals === 'object' ? sourceLocals : {};
|
|
294
|
+
const input = options && typeof options === 'object' ? options : {};
|
|
295
|
+
const siteName = input.siteName || locals.siteName || config.locals.siteName || 'ASJS';
|
|
296
|
+
const navItems = normalizeNavItems(
|
|
297
|
+
Array.isArray(input.navItems)
|
|
298
|
+
? input.navItems
|
|
299
|
+
: (Array.isArray(locals.navItems) ? locals.navItems : config.navItems)
|
|
300
|
+
);
|
|
301
|
+
const brand = normalizeBrandOptions(
|
|
302
|
+
Object.prototype.hasOwnProperty.call(input, 'brand')
|
|
303
|
+
? input.brand
|
|
304
|
+
: (Object.prototype.hasOwnProperty.call(locals, 'brand') ? locals.brand : config.brand),
|
|
305
|
+
{ name: siteName }
|
|
306
|
+
);
|
|
307
|
+
const siteCta = normalizeSiteCtaOptions(
|
|
308
|
+
Object.prototype.hasOwnProperty.call(input, 'siteCta')
|
|
309
|
+
? input.siteCta
|
|
310
|
+
: (Object.prototype.hasOwnProperty.call(locals, 'siteCta') ? locals.siteCta : config.siteCta),
|
|
311
|
+
config.siteCta
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
brand,
|
|
316
|
+
currentPath: input.currentPath || locals.currentPath || '/',
|
|
317
|
+
isActiveNavItem: typeof input.isActiveNavItem === 'function'
|
|
318
|
+
? input.isActiveNavItem
|
|
319
|
+
: (typeof locals.isActiveNavItem === 'function' ? locals.isActiveNavItem : isActiveNavItem),
|
|
320
|
+
navItems,
|
|
321
|
+
note: input.note || locals.headerNote || '',
|
|
322
|
+
noteLabel: input.noteLabel || locals.headerNoteLabel || '',
|
|
323
|
+
siteCta,
|
|
324
|
+
siteName
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function renderHeaderMarkup(options = {}) {
|
|
329
|
+
const brand = options.brand || normalizeBrandOptions();
|
|
330
|
+
const currentPath = options.currentPath || '/';
|
|
331
|
+
const resolveActive = typeof options.isActiveNavItem === 'function' ? options.isActiveNavItem : isActiveNavItem;
|
|
332
|
+
const navigation = normalizeNavItems(options.navItems);
|
|
333
|
+
const note = options.note ? String(options.note) : '';
|
|
334
|
+
const noteLabel = options.noteLabel ? String(options.noteLabel) : '';
|
|
335
|
+
const cta = normalizeSiteCtaOptions(options.siteCta);
|
|
336
|
+
|
|
337
|
+
return [
|
|
338
|
+
'<header class="example-header">',
|
|
339
|
+
note
|
|
340
|
+
? ` <div class="header-note"><span class="example-pill">${escapeHtml(noteLabel || 'Site Header')}</span><p>${escapeHtml(note)}</p></div>`
|
|
341
|
+
: '',
|
|
342
|
+
' <div class="header-main">',
|
|
343
|
+
` <a${serializeHtmlAttributes({
|
|
344
|
+
class: 'brand-link',
|
|
345
|
+
href: brand.href || '/',
|
|
346
|
+
'data-asjs-link': true,
|
|
347
|
+
...(brand.transition ? { 'data-asjs-transition': brand.transition } : {})
|
|
348
|
+
})}><span class="brand-mark">${escapeHtml(brand.mark || 'AS')}</span><span class="brand-copy"><strong>${escapeHtml(brand.name || options.siteName || 'ASJS')}</strong><span>${escapeHtml(brand.tagline || 'Server-rendered interface shell for Express')}</span>${brand.caption ? `<small>${escapeHtml(brand.caption)}</small>` : ''}</span></a>`,
|
|
349
|
+
navigation.length
|
|
350
|
+
? ` <nav class="site-nav" aria-label="Primary navigation">${navigation.map((item) => {
|
|
351
|
+
const isActive = resolveActive(currentPath, item);
|
|
352
|
+
|
|
353
|
+
return `<a${serializeHtmlAttributes({
|
|
354
|
+
href: item.href,
|
|
355
|
+
class: `site-link${isActive ? ' is-active' : ''}`,
|
|
356
|
+
'data-asjs-link': true,
|
|
357
|
+
'data-asjs-active': item.activeMode || 'exact',
|
|
358
|
+
...(item.matchPath ? { 'data-asjs-match-path': item.matchPath } : {}),
|
|
359
|
+
...(item.transition ? { 'data-asjs-transition': item.transition } : {}),
|
|
360
|
+
...(isActive ? { 'aria-current': 'page' } : {})
|
|
361
|
+
})}><span>${escapeHtml(item.label)}</span>${item.description ? `<small>${escapeHtml(item.description)}</small>` : ''}</a>`;
|
|
362
|
+
}).join('')}</nav>`
|
|
363
|
+
: '',
|
|
364
|
+
cta
|
|
365
|
+
? ` <a${serializeHtmlAttributes({
|
|
366
|
+
class: 'button button-secondary header-cta',
|
|
367
|
+
href: cta.href,
|
|
368
|
+
'data-asjs-link': true,
|
|
369
|
+
...(cta.transition ? { 'data-asjs-transition': cta.transition } : {})
|
|
370
|
+
})}>${escapeHtml(cta.label)}</a>`
|
|
371
|
+
: '',
|
|
372
|
+
' </div>',
|
|
373
|
+
'</header>'
|
|
374
|
+
].filter(Boolean).join('');
|
|
375
|
+
}
|
|
376
|
+
|
|
187
377
|
function normalizeFormMode(value, fallback = 'view') {
|
|
188
378
|
const normalized = String(value || fallback || 'view').toLowerCase();
|
|
189
379
|
return ['view', 'json'].includes(normalized) ? normalized : (fallback || 'view');
|
|
@@ -440,6 +630,7 @@ function createAsjsViewModel(inputAsjs, config, overrides = {}) {
|
|
|
440
630
|
viewModel.viewAttrs = (extraAttributes) => renderViewAttrs(viewModel, extraAttributes);
|
|
441
631
|
viewModel.formAttrs = (options) => renderFormAttrs(viewModel, options);
|
|
442
632
|
viewModel.progressMarkup = () => renderProgressMarkup();
|
|
633
|
+
viewModel.header = (options) => renderHeaderMarkup(resolveHeaderState(config, {}, options));
|
|
443
634
|
|
|
444
635
|
return viewModel;
|
|
445
636
|
}
|
|
@@ -957,7 +1148,14 @@ function createAsjsConfig(options = {}) {
|
|
|
957
1148
|
defaultLayout: options.defaultLayout || null,
|
|
958
1149
|
debug: Boolean(options.debug),
|
|
959
1150
|
cache: normalizeBooleanOption(options.cache, !options.debug),
|
|
960
|
-
navItems:
|
|
1151
|
+
navItems: normalizeNavItems(options.navItems),
|
|
1152
|
+
brand: normalizeBrandOptions(
|
|
1153
|
+
Object.prototype.hasOwnProperty.call(options, 'brand') ? options.brand : undefined,
|
|
1154
|
+
{ name: options.locals && options.locals.siteName ? options.locals.siteName : 'ASJS' }
|
|
1155
|
+
),
|
|
1156
|
+
siteCta: normalizeSiteCtaOptions(
|
|
1157
|
+
Object.prototype.hasOwnProperty.call(options, 'siteCta') ? options.siteCta : undefined
|
|
1158
|
+
),
|
|
961
1159
|
locals: options.locals && typeof options.locals === 'object' ? options.locals : {},
|
|
962
1160
|
components: normalizeComponentRegistry(options.components || {}, extension),
|
|
963
1161
|
templateCache: new Map(),
|
|
@@ -1378,9 +1576,35 @@ function buildPageLocals(req, config, pageData = {}) {
|
|
|
1378
1576
|
delete locals.transition;
|
|
1379
1577
|
delete locals.asjs;
|
|
1380
1578
|
|
|
1381
|
-
|
|
1579
|
+
const headerOverrides = {
|
|
1580
|
+
currentPath: input.currentPath || req.path
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
if (Array.isArray(input.navItems)) {
|
|
1584
|
+
headerOverrides.navItems = input.navItems;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
if (Object.prototype.hasOwnProperty.call(input, 'brand')) {
|
|
1588
|
+
headerOverrides.brand = input.brand;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (Object.prototype.hasOwnProperty.call(input, 'siteCta')) {
|
|
1592
|
+
headerOverrides.siteCta = input.siteCta;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (typeof input.isActiveNavItem === 'function') {
|
|
1596
|
+
headerOverrides.isActiveNavItem = input.isActiveNavItem;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const headerState = resolveHeaderState(config, locals, headerOverrides);
|
|
1600
|
+
|
|
1601
|
+
locals.brand = headerState.brand;
|
|
1602
|
+
locals.isActiveNavItem = headerState.isActiveNavItem;
|
|
1603
|
+
locals.navItems = headerState.navItems;
|
|
1382
1604
|
locals.currentPath = input.currentPath || req.path;
|
|
1383
1605
|
locals.asjs = createAsjsViewModel(pageAsjs, config, { transition });
|
|
1606
|
+
locals.siteCta = headerState.siteCta;
|
|
1607
|
+
locals.asjs.header = (options) => renderHeaderMarkup(resolveHeaderState(config, locals, options));
|
|
1384
1608
|
|
|
1385
1609
|
return {
|
|
1386
1610
|
locals,
|
|
@@ -1574,6 +1798,7 @@ module.exports = {
|
|
|
1574
1798
|
getAsjsPackagePaths,
|
|
1575
1799
|
renderClientTags,
|
|
1576
1800
|
renderDebugErrorPage,
|
|
1801
|
+
renderHeaderMarkup,
|
|
1577
1802
|
validateComponentProps,
|
|
1578
1803
|
setupAsjs
|
|
1579
1804
|
};
|
package/package.json
CHANGED