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