Add /_lib/get_globals.jst
[ndcode_site.git] / _lib / navbar.jst
1 let assert = require('assert')
2 let XDate = require('xdate')
3
4 return async (env, head, body, scripts) => {
5   //let cart = await _require('/online_store/cart.jst')
6   let get_globals = await _require('/_lib/get_globals.jst')
7   let get_session = await _require('/_lib/get_session.jst')
8   //let icon_cart_small = await env.site.get_min_svg('/_svg/icon_cart_small.svg')
9   let icon_search_mono = await env.site.get_min_svg('/_svg/icon_search_mono.svg')
10   let logo_large = await env.site.get_min_svg('/_svg/logo_large.svg')
11   let menu = await env.site.get_menu('/_menu.json')
12   let page = await _require('/_lib/page.jst')
13
14   // initialize env.cart
15   //await cart(env)
16
17   let transaction = await env.site.Transaction()
18   let signed_in_as, site_title, copyright
19   try {
20     let session = await get_session(env, transaction)
21     signed_in_as = session.get_json('signed_in_as', null)
22
23     let globals = await get_globals(env, transaction)
24     site_title = globals.get_json('site_title')
25     copyright = globals.get_json('copyright')
26   }
27   finally {
28     transaction.rollback()
29   }
30
31   await page(
32     env,
33     // head
34     async _out => {
35       // extract top-level directory name
36       assert(env.parsed_url.pathname.slice(0, 1) === '/')
37       let index = env.parsed_url.pathname.indexOf('/', 1)
38       let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
39
40       title {
41         `${site_title}: ${
42           dir.length === 0 ?
43             'Home' :
44             menu.entries[menu.index[dir]].name
45           }`
46       }
47
48       await head(_out)
49     },
50     // body
51     async _out => {
52       // extract top-level directory name
53       assert(env.parsed_url.pathname.slice(0, 1) === '/')
54       let index = env.parsed_url.pathname.indexOf('/', 1)
55       let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
56
57       div.scrollbar-fix {
58         div.container {
59           div.row.align-items-center.py-3 {
60             div.col-sm-8 {
61               _out.push(logo_large)
62             }
63             div.'col-sm-4' {
64               div.'mb-1'.text-right {
65                 span#signed-in-status {
66                   if (signed_in_as !== null)
67                     'Signed in.' //`Signed in as ${signed_in_as}.`
68                   else
69                     'Browsing as guest.'
70                 }
71                 ' '
72                 if (signed_in_as !== null)
73                   a#sign-in(href="#" style="display: none;") {'Sign in'}
74                 else
75                   a#sign-in(href="#") {'Sign in'}
76                 ' '
77                 if (signed_in_as !== null)
78                   a#sign-up(href="/my_account/sign_up/index.html" style="display: none;") {'Sign up'}
79                 else
80                   a#sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
81                 ' '
82                 if (signed_in_as !== null)
83                   a#sign-out(href="#") {'Sign out'}
84                 else
85                   a#sign-out(href="#" style="display: none;") {'Sign out'}
86               }
87   
88               form(action="/search/index.html") {
89                 div.input-group {
90                   input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
91                   div.input-group-append {
92                     button.btn.btn-outline-secondary#search-button(type="submit") {
93                       _out.push(icon_search_mono)
94                     }
95                   }
96                 }
97               }
98             }
99   
100             //div.'col-sm-1'.vbottom {
101             //  // a nested div is used to avoid hover colour on the padding
102             //  div.nav-li-a(style="text-align: center;") {
103             //    a(href="/online_store/view_cart/index.html") {
104             //      div.cart-icon {
105             //        _out.push(icon_cart_small)
106             //      }
107             //      div.cart-number {
108             //        div.cart-circle {
109             //          `${(env.cart.items || []).length}`
110             //        }
111             //      }
112             //    }
113             //  }
114             //}
115           }
116         }
117       }
118       nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.scrollbar-fix {
119         div.container {
120           //a.navbar-brand(href="#") {'Navbar'}
121           //' '
122           button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation") {
123             span.navbar-toggler-icon {}
124           }
125           div.collapse.navbar-collapse#navbarSupportedContent {
126             ul.navbar-nav.mr-auto {
127               if (dir.length === 0)
128                 li.nav-item.active {
129                   a.nav-link(href="/index.html") {
130                     'Home'
131                     span.sr-only {' (current)'}
132                   }
133                 }
134               else
135                 li.nav-item {
136                   a.nav-link(href="/index.html") {'Home'}
137                 }
138               let entries = menu.entries
139               for (let i = 0; i < entries.length; ++i)
140                 if (entries[i].navbar)
141                   if (entries[i].dir === dir)
142                     li.nav-item.active {
143                       a.nav-link(href=`/${entries[i].dir}/index.html`) {
144                         `${entries[i].name}`
145                         span.sr-only {' (current)'}
146                       }
147                     }
148                   else
149                     li.nav-item {
150                       a.nav-link(href=`/${entries[i].dir}/index.html`) {
151                         `${entries[i].name}`
152                       }
153                     }
154               //li.nav-item.dropdown {
155               //  a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
156               //    'Dropdown'
157               //  }
158               //  div.dropdown-menu(aria-labelledby="navbarDropdown") {
159               //    a.dropdown-item(href="#") {
160               //      'Action'
161               //    }
162               //    ' '
163               //    a.dropdown-item(href="#") {
164               //      'Another action'
165               //    }
166               //    div.dropdown-divider {}
167               //    a.dropdown-item(href="#") {
168               //      'Something else here'
169               //    }
170               //  }
171               //}
172               //li.nav-item {
173               //  a.nav-link.disabled {
174               //    'Disabled'
175               //  }
176               //}
177             }
178             ul.navbar-nav.ml-auto {
179               li.nav-item {
180                 a.nav-link#give-feedback(href="#") {'Give feedback'}
181               }
182             }
183           }
184         }
185       }
186       div.scrollbar-fix {
187         div.container {
188           await body(_out)
189         }
190       }
191       footer.scrollbar-fix {
192         div.container {
193           a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
194             img(alt="Creative Commons License" style="border-width:0;" src="/images/by-sa_3.0_88x31.png") {}
195           }
196           p {
197             'This website is '
198             a(href="https://git.ndcode.org/public/ndcode_site.git") {
199               'open source'
200             }
201             ' and licensed under a '
202             a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
203               'Creative Commons Attribution-ShareAlike 3.0 Unported License'
204             }
205             '.'
206           }
207
208           p {'Example code fragments embedded within the text are placed in the public domain unless otherwise noted.'}
209
210           p {`Copyright © ${new XDate(env.now).getUTCFullYear()} ${copyright}.`}
211         }
212       }
213
214       // hidden part
215       div#sign-in-modal.modal.fade(role="dialog") {
216         div.modal-dialog {
217           div.modal-content {
218             div.modal-header {
219               span.h4.modal-title {'Sign in'}
220             }
221             div.modal-body {
222               div.row {
223                 div.col-md-12 {
224                   div.form-group {
225                     label.form-label(for="sign-in-email") {'Email'}
226                     input.form-control#sign-in-email(type="text" placeholder="Account email address" required="required" maxlength=256) {}
227                   }
228                 }
229               }
230               div.row {
231                 div.col-md-12 {
232                   div.form-group {
233                     label.form-label(for="sign-in-password") {'Password'}
234                     input.form-control#sign-in-password(type="password" placeholder="Account password" required="required" minlength=8 maxlength=256) {}
235                   }
236                 }
237               }
238
239               p.mt-2 {
240                 'No account yet? '
241                 a(href="/my_account/sign_up/index.html") {'Sign up'}
242               }
243
244               p {
245                 'Forgot password? '
246                 a(href="/my_account/password_reset/index.html") {'Password reset'}
247               }
248             }
249             div.modal-footer {
250               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
251                 'Cancel'
252               }
253               button.btn.btn-primary#sign-in-submit(type="button") {
254                 'Sign in'
255               }
256             }
257           }
258         }
259       }
260
261       div#feedback-modal.modal.fade(role="dialog") {
262         div.modal-dialog {
263           div.modal-content {
264             div.modal-header {
265               span.h4.modal-title {'Give feedback'}
266             }
267             div.modal-body {
268               p {
269                 'Did you notice something not quite right, or just want to share your impression of this page?'
270               }
271               div.row {
272                 div.col-md-12 {
273                   div.form-group {
274                     label.form-label(for="feedback-message") {'Message'}
275                     textarea.form-control#feedback-message(placeholder="Please tell us your thoughts" required="required" rows=4 maxlength=65536) {}
276                   }
277                 }
278               }
279             }
280             div.modal-footer {
281               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
282                 'Cancel'
283               }
284               button.btn.btn-primary#feedback-submit(type="button") {
285                 'Submit'
286               }
287             }
288           }
289         }
290       }
291
292       div#message-modal.modal.fade(role="dialog") {
293         div.modal-dialog {
294           div.modal-content {
295             div.modal-header {
296               span.h4.modal-title {'Message'}
297             }
298             div.modal-body#message-modal-message {
299             }
300             div.modal-footer {
301               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
302                 'Close'
303               }
304             }
305           }
306         }
307       }
308     },
309     // scripts
310     async _out => {
311       script(src="/js/api_call.js") {}
312
313       script {
314         let api_account_sign_in = async (...args) => api_call(
315           '/api/account/sign_in.json',
316           ...args
317         )
318         let api_account_sign_out = async (...args) => api_call(
319           '/api/account/sign_out.json',
320           ...args
321         )
322         let api_feedback = async (...args) => api_call(
323           '/api/feedback.json',
324           ...args
325         )
326
327         // this function can be overridden in a further script
328         function sign_in_out(status) {
329         }
330
331         document.addEventListener(
332           'DOMContentLoaded',
333           () => {
334             // sign in form
335             document.getElementById('sign-in').addEventListener(
336               'click',
337               () => {
338                 document.getElementById('sign-in-email').value = ''
339                 document.getElementById('sign-in-password').value = ''
340                 $('#sign-in-modal').modal('show')
341               }
342             )
343
344             $('#sign-in-modal').on(
345               'shown.bs.modal',
346               () => {
347                 console.log('bloo')
348                 $('#sign-in-email').focus()
349               }
350             )
351
352             document.getElementById('sign-in-submit').addEventListener(
353               'click',
354               async () => {
355                 let email
356                 try {
357                   email = document.getElementById('sign-in-email').value.slice(0, 256).toLowerCase()
358                   await api_account_sign_in(
359                     email,
360                     document.getElementById('sign-in-password').value.slice(0, 256)
361                   )
362                 }
363                 catch (error) {
364                   let problem =
365                     error instanceof Problem ?
366                       error :
367                       new Problem(
368                         // title
369                         'Bad request',
370                         // details
371                         (error.stack || error.message).toString()
372                         // status
373                         400
374                       )
375
376                   if (problem.title === 'Email not yet verified') {
377                     location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
378                     return
379                   }
380
381                   document.getElementById('message-modal-message').textContent = problem.detail
382                   $('#sign-in-modal').modal('hide')
383                   $('#message-modal').modal('show')
384                   return
385                 }
386
387                 document.getElementById('signed-in-status').textContent = 'Signed in.' //`Signed in as ${email}.`
388                 $('#sign-in').hide()
389                 $('#sign-up').hide()
390                 $('#sign-out').show()
391                 sign_in_out(true)
392
393                 document.getElementById('message-modal-message').textContent = `You are now signed in as "${email}".`
394                 $('#sign-in-modal').modal('hide')
395                 $('#message-modal').modal('show')
396               }
397             )
398
399             // sign out button
400             document.getElementById('sign-out').addEventListener(
401               'click',
402               async () => {
403                 try {
404                   await api_account_sign_out()
405                 }
406                 catch (error) {
407                   let problem =
408                     error instanceof Problem ?
409                       error :
410                       new Problem(
411                         // title
412                         'Bad request',
413                         // details
414                         (error.stack || error.message).toString()
415                         // status
416                         400
417                       )
418
419                   document.getElementById('message-modal-message').textContent = problem.detail
420                   $('#sign-in-modal').modal('hide')
421                   $('#message-modal').modal('show')
422                   return
423                 }
424
425                 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
426                 $('#sign-in').show()
427                 $('#sign-up').show()
428                 $('#sign-out').hide()
429                 sign_in_out(false)
430
431                 document.getElementById('message-modal-message').textContent = `You are now signed out.`
432                 $('#sign-in-modal').modal('hide')
433                 $('#message-modal').modal('show')
434               }
435             )
436
437             // feedback form
438             document.getElementById('give-feedback').addEventListener(
439               'click',
440               () => {
441                 $('#feedback-message').text('')
442                 $('#feedback-modal').modal('show')
443                 return false
444               }
445             )
446
447             $('#feedback-modal').on(
448               'shown.bs.modal',
449               () => {
450                 $('#feedback-message').focus()
451               }
452             )
453
454             document.getElementById('feedback-submit').addEventListener(
455               'click',
456               async () => {
457                 try {
458                   await api_feedback(
459                     location.href,
460                     document.getElementById('feedback-message').value.slice(0, 65536)
461                   )
462                 }
463                 catch (error) {
464                   let problem =
465                     error instanceof Problem ?
466                       error :
467                       new Problem(
468                         // title
469                         'Bad request',
470                         // details
471                         (error.stack || error.message).toString()
472                         // status
473                         400
474                       )
475
476                   document.getElementById('message-modal-message').textContent = problem.detail
477                   $('#feedback-modal').modal('hide')
478                   $('#message-modal').modal('show')
479                   return
480                 }
481
482                 document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
483                 $('#feedback-modal').modal('hide')
484                 $('#message-modal').modal('show')
485               }
486             )
487           }
488         )
489       }
490
491       await scripts(_out)
492     }
493   )
494 }