1 let assert = require('assert')
2 let XDate = require('xdate')
4 return async (env, head, body, scripts) => {
5 //let cart = await _require('/online_store/cart.jst')
6 let get_session = await _require('/_lib/get_session.jst')
7 //let icon_cart_small = await env.site.get_min_svg('/_svg/icon_cart_small.svg')
8 let icon_search_mono = await env.site.get_min_svg('/_svg/icon_search_mono.svg')
9 let logo_large = await env.site.get_min_svg('/_svg/logo_large.svg')
10 let menu = await env.site.get_menu('/_menu.json')
11 let page = await _require('/_lib/page.jst')
13 // initialize env.cart
16 let transaction = await env.site.Transaction()
19 let session = await get_session(env, transaction)
20 signed_in_as = session.get_json('signed_in_as', null)
23 transaction.rollback()
30 // extract top-level directory name
31 assert(env.parsed_url.pathname.slice(0, 1) === '/')
32 let index = env.parsed_url.pathname.indexOf('/', 1)
33 let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
36 let transaction = await env.site.database.Transaction()
38 let globals = await (await transaction.get({})).get('globals')
39 ;`${await globals.get_json('site_title')}: ${
42 menu.entries[menu.index[dir]].name
46 transaction.rollback()
54 // extract top-level directory name
55 assert(env.parsed_url.pathname.slice(0, 1) === '/')
56 let index = env.parsed_url.pathname.indexOf('/', 1)
57 let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
61 div.row.align-items-center.py-3 {
66 div.'mb-1'.text-right {
67 span#signed-in-status {
68 if (signed_in_as !== null)
69 'Signed in.' //`Signed in as ${signed_in_as}.`
74 if (signed_in_as !== null)
75 a#sign-in(href="#" style="display: none;") {'Sign in'}
77 a#sign-in(href="#") {'Sign in'}
79 if (signed_in_as !== null)
80 a#sign-up(href="/my_account/sign_up/index.html" style="display: none;") {'Sign up'}
82 a#sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
84 if (signed_in_as !== null)
85 a#sign-out(href="#") {'Sign out'}
87 a#sign-out(href="#" style="display: none;") {'Sign out'}
90 form(action="/search/index.html") {
92 input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
93 div.input-group-append {
94 button.btn.btn-outline-secondary#search-button(type="submit") {
95 _out.push(icon_search_mono)
102 //div.'col-sm-1'.vbottom {
103 // // a nested div is used to avoid hover colour on the padding
104 // div.nav-li-a(style="text-align: center;") {
105 // a(href="/online_store/view_cart/index.html") {
107 // _out.push(icon_cart_small)
111 // `${(env.cart.items || []).length}`
120 nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.scrollbar-fix {
122 //a.navbar-brand(href="#") {'Navbar'}
124 button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation") {
125 span.navbar-toggler-icon {}
127 div.collapse.navbar-collapse#navbarSupportedContent {
128 ul.navbar-nav.mr-auto {
129 if (dir.length === 0)
131 a.nav-link(href="/index.html") {
133 span.sr-only {' (current)'}
138 a.nav-link(href="/index.html") {'Home'}
140 let entries = menu.entries
141 for (let i = 0; i < entries.length; ++i)
142 if (entries[i].navbar)
143 if (entries[i].dir === dir)
145 a.nav-link(href=`/${entries[i].dir}/index.html`) {
147 span.sr-only {' (current)'}
152 a.nav-link(href=`/${entries[i].dir}/index.html`) {
156 //li.nav-item.dropdown {
157 // a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
160 // div.dropdown-menu(aria-labelledby="navbarDropdown") {
161 // a.dropdown-item(href="#") {
165 // a.dropdown-item(href="#") {
168 // div.dropdown-divider {}
169 // a.dropdown-item(href="#") {
170 // 'Something else here'
175 // a.nav-link.disabled {
180 ul.navbar-nav.ml-auto {
182 a.nav-link#give-feedback(href="#") {'Give feedback'}
193 footer.scrollbar-fix {
195 a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
196 img(alt="Creative Commons License" style="border-width:0;" src="/images/by-sa_3.0_88x31.png") {}
200 a(href="https://git.ndcode.org/public/ndcode_site.git") {
203 ' and licensed under a '
204 a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
205 'Creative Commons Attribution-ShareAlike 3.0 Unported License'
210 p {'Example code fragments embedded within the text are placed in the public domain unless otherwise noted.'}
213 let transaction = await env.site.database.Transaction()
215 let globals = await (await transaction.get({})).get('globals')
216 ;`Copyright © ${new XDate().getUTCFullYear()} ${await globals.get('copyright')}.`
219 transaction.rollback()
226 div#sign-in-modal.modal.fade(role="dialog") {
230 span.h4.modal-title {'Sign in'}
236 label.form-label(for="sign-in-email") {'Email'}
237 input.form-control#sign-in-email(type="text" placeholder="Account email address" required="required" maxlength=256) {}
244 label.form-label(for="sign-in-password") {'Password'}
245 input.form-control#sign-in-password(type="password" placeholder="Account password" required="required" minlength=8 maxlength=256) {}
252 a(href="/my_account/sign_up/index.html") {'Sign up'}
257 a(href="/my_account/password_reset/index.html") {'Password reset'}
261 button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
264 button.btn.btn-primary#sign-in-submit(type="button") {
272 div#feedback-modal.modal.fade(role="dialog") {
276 span.h4.modal-title {'Give feedback'}
280 'Did you notice something not quite right, or just want to share your impression of this page?'
285 label.form-label(for="feedback-message") {'Message'}
286 textarea.form-control#feedback-message(placeholder="Please tell us your thoughts" required="required" rows=4 maxlength=65536) {}
292 button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
295 button.btn.btn-primary#feedback-submit(type="button") {
303 div#message-modal.modal.fade(role="dialog") {
307 span.h4.modal-title {'Message'}
309 div.modal-body#message-modal-message {
312 button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
322 script(src="/js/api_call.js") {}
325 let api_account_sign_in = async (...args) => api_call(
326 '/api/account/sign_in.json',
329 let api_account_sign_out = async (...args) => api_call(
330 '/api/account/sign_out.json',
333 let api_feedback = async (...args) => api_call(
334 '/api/feedback.json',
338 // this function can be overridden in a further script
339 function sign_in_out(status) {
342 document.addEventListener(
346 document.getElementById('sign-in').addEventListener(
349 document.getElementById('sign-in-email').value = ''
350 document.getElementById('sign-in-password').value = ''
351 $('#sign-in-modal').modal('show')
355 $('#sign-in-modal').on(
359 $('#sign-in-email').focus()
363 document.getElementById('sign-in-submit').addEventListener(
368 email = document.getElementById('sign-in-email').value.slice(0, 256).toLowerCase()
369 await api_account_sign_in(
371 document.getElementById('sign-in-password').value.slice(0, 256)
376 error instanceof Problem ?
382 (error.stack || error.message).toString()
387 if (problem.title === 'Email not yet verified') {
388 location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
392 document.getElementById('message-modal-message').textContent = problem.detail
393 $('#sign-in-modal').modal('hide')
394 $('#message-modal').modal('show')
398 document.getElementById('signed-in-status').textContent = 'Signed in.' //`Signed in as ${email}.`
401 $('#sign-out').show()
404 document.getElementById('message-modal-message').textContent = `You are now signed in as "${email}".`
405 $('#sign-in-modal').modal('hide')
406 $('#message-modal').modal('show')
411 document.getElementById('sign-out').addEventListener(
415 await api_account_sign_out()
419 error instanceof Problem ?
425 (error.stack || error.message).toString()
430 document.getElementById('message-modal-message').textContent = problem.detail
431 $('#sign-in-modal').modal('hide')
432 $('#message-modal').modal('show')
436 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
439 $('#sign-out').hide()
442 document.getElementById('message-modal-message').textContent = `You are now signed out.`
443 $('#sign-in-modal').modal('hide')
444 $('#message-modal').modal('show')
449 document.getElementById('give-feedback').addEventListener(
452 $('#feedback-message').text('')
453 $('#feedback-modal').modal('show')
458 $('#feedback-modal').on(
461 $('#feedback-message').focus()
465 document.getElementById('feedback-submit').addEventListener(
471 document.getElementById('feedback-message').value.slice(0, 65536)
476 error instanceof Problem ?
482 (error.stack || error.message).toString()
487 document.getElementById('message-modal-message').textContent = problem.detail
488 $('#feedback-modal').modal('hide')
489 $('#message-modal').modal('show')
493 document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
494 $('#feedback-modal').modal('hide')
495 $('#message-modal').modal('show')