4636c522df139be0bcc7cabc4ce8e841f6bc33a3
[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_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')
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', null)
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#signed-in-status {
68                   if (signed_in_as !== null)
69                     'Signed in.' //`Signed in as ${signed_in_as}.`
70                   else
71                     'Browsing as guest.'
72                 }
73                 ' '
74                 if (signed_in_as !== null)
75                   a#sign-in(href="#" style="display: none;") {'Sign in'}
76                 else
77                   a#sign-in(href="#") {'Sign in'}
78                 ' '
79                 if (signed_in_as !== null)
80                   a#sign-up(href="/my_account/sign_up/index.html" style="display: none;") {'Sign up'}
81                 else
82                   a#sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
83                 ' '
84                 if (signed_in_as !== null)
85                   a#sign-out(href="#") {'Sign out'}
86                 else
87                   a#sign-out(href="#" style="display: none;") {'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#search-button(type="submit") {
95                       _out.push(icon_search_mono)
96                     }
97                   }
98                 }
99               }
100             }
101
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") {
106             //      div.cart-icon {
107             //        _out.push(icon_cart_small)
108             //      }
109             //      div.cart-number {
110             //        div.cart-circle {
111             //          `${(env.cart.items || []).length}`
112             //        }
113             //      }
114             //    }
115             //  }
116             //}
117           }
118         }
119       }
120       nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.scrollbar-fix {
121         div.container {
122           //a.navbar-brand(href="#") {'Navbar'}
123           //' '
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 {}
126           }
127           div.collapse.navbar-collapse#navbarSupportedContent {
128             ul.navbar-nav.mr-auto {
129               if (dir.length === 0)
130                 li.nav-item.active {
131                   a.nav-link(href="/index.html") {
132                     'Home'
133                     span.sr-only {' (current)'}
134                   }
135                 }
136               else
137                 li.nav-item {
138                   a.nav-link(href="/index.html") {'Home'}
139                 }
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)
144                     li.nav-item.active {
145                       a.nav-link(href=`/${entries[i].dir}/index.html`) {
146                         `${entries[i].name}`
147                         span.sr-only {' (current)'}
148                       }
149                     }
150                   else
151                     li.nav-item {
152                       a.nav-link(href=`/${entries[i].dir}/index.html`) {
153                         `${entries[i].name}`
154                       }
155                     }
156               //li.nav-item.dropdown {
157               //  a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
158               //    'Dropdown'
159               //  }
160               //  div.dropdown-menu(aria-labelledby="navbarDropdown") {
161               //    a.dropdown-item(href="#") {
162               //      'Action'
163               //    }
164               //    ' '
165               //    a.dropdown-item(href="#") {
166               //      'Another action'
167               //    }
168               //    div.dropdown-divider {}
169               //    a.dropdown-item(href="#") {
170               //      'Something else here'
171               //    }
172               //  }
173               //}
174               //li.nav-item {
175               //  a.nav-link.disabled {
176               //    'Disabled'
177               //  }
178               //}
179             }
180             ul.navbar-nav.ml-auto {
181               li.nav-item {
182                 a.nav-link#give-feedback(href="#") {'Give feedback'}
183               }
184             }
185           }
186         }
187       }
188       div.scrollbar-fix {
189         div.container {
190           await body(_out)
191         }
192       }
193       footer.scrollbar-fix {
194         div.container {
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") {}
197           }
198           p {
199             'This website is '
200             a(href="https://git.ndcode.org/public/ndcode_site.git") {
201               'open source'
202             }
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'
206             }
207             '.'
208           }
209
210           p {'Example code fragments embedded within the text are placed in the public domain unless otherwise noted.'}
211
212           p {`Copyright © ${new XDate(env.now).getUTCFullYear()} ${copyright}.`}
213         }
214       }
215
216       // hidden part
217       div#sign-in-modal.modal.fade(role="dialog") {
218         div.modal-dialog {
219           div.modal-content {
220             div.modal-header {
221               span.h4.modal-title {'Sign in'}
222             }
223             div.modal-body {
224               div.row {
225                 div.col-md-12 {
226                   div.form-group {
227                     label.form-label(for="sign-in-email") {'Email'}
228                     input.form-control#sign-in-email(type="text" placeholder="Account email address" required="required" maxlength=256) {}
229                   }
230                 }
231               }
232               div.row {
233                 div.col-md-12 {
234                   div.form-group {
235                     label.form-label(for="sign-in-password") {'Password'}
236                     input.form-control#sign-in-password(type="password" placeholder="Account password" required="required" minlength=8 maxlength=256) {}
237                   }
238                 }
239               }
240
241               p.mt-2 {
242                 'No account yet? '
243                 a(href="/my_account/sign_up/index.html") {'Sign up'}
244               }
245
246               p {
247                 'Forgot password? '
248                 a(href="/my_account/password_reset/index.html") {'Password reset'}
249               }
250             }
251             div.modal-footer {
252               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
253                 'Cancel'
254               }
255               button.btn.btn-primary#sign-in-submit(type="button") {
256                 'Sign in'
257               }
258             }
259           }
260         }
261       }
262
263       div#feedback-modal.modal.fade(role="dialog") {
264         div.modal-dialog {
265           div.modal-content {
266             div.modal-header {
267               span.h4.modal-title {'Give feedback'}
268             }
269             div.modal-body {
270               p {
271                 'Did you notice something not quite right, or just want to share your impression of this page?'
272               }
273               div.row {
274                 div.col-md-12 {
275                   div.form-group {
276                     label.form-label(for="feedback-message") {'Message'}
277                     textarea.form-control#feedback-message(placeholder="Please tell us your thoughts" required="required" rows=4 maxlength=65536) {}
278                   }
279                 }
280               }
281             }
282             div.modal-footer {
283               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
284                 'Cancel'
285               }
286               button.btn.btn-primary#feedback-submit(type="button") {
287                 'Submit'
288               }
289             }
290           }
291         }
292       }
293
294       div#message-modal.modal.fade(role="dialog") {
295         div.modal-dialog {
296           div.modal-content {
297             div.modal-header {
298               span.h4.modal-title {'Message'}
299             }
300             div.modal-body#message-modal-message {
301             }
302             div.modal-footer {
303               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
304                 'Close'
305               }
306             }
307           }
308         }
309       }
310     },
311     // scripts
312     async _out => {
313       script(src="/js/api_call.js") {}
314
315       script {
316         let api_account_sign_in = async (...args) => api_call(
317           '/api/account/sign_in.json',
318           ...args
319         )
320         let api_account_sign_out = async (...args) => api_call(
321           '/api/account/sign_out.json',
322           ...args
323         )
324         let api_feedback = async (...args) => api_call(
325           '/api/feedback.json',
326           ...args
327         )
328
329         // this function can be overridden in a further script
330         function sign_in_out(status) {
331         }
332
333         document.addEventListener(
334           'DOMContentLoaded',
335           () => {
336             // sign in form
337             document.getElementById('sign-in').addEventListener(
338               'click',
339               () => {
340                 document.getElementById('sign-in-email').value = ''
341                 document.getElementById('sign-in-password').value = ''
342                 $('#sign-in-modal').modal('show')
343               }
344             )
345
346             $('#sign-in-modal').on(
347               'shown.bs.modal',
348               () => {
349                 console.log('bloo')
350                 $('#sign-in-email').focus()
351               }
352             )
353
354             document.getElementById('sign-in-submit').addEventListener(
355               'click',
356               async () => {
357                 let email
358                 try {
359                   email = document.getElementById('sign-in-email').value.slice(0, 256).toLowerCase()
360                   await api_account_sign_in(
361                     email,
362                     document.getElementById('sign-in-password').value.slice(0, 256)
363                   )
364                 }
365                 catch (error) {
366                   let problem =
367                     error instanceof Problem ?
368                       error :
369                       new Problem(
370                         // title
371                         'Bad request',
372                         // details
373                         (error.stack || error.message).toString()
374                         // status
375                         400
376                       )
377
378                   if (problem.title === 'Email not yet verified') {
379                     location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
380                     return
381                   }
382
383                   document.getElementById('message-modal-message').textContent = problem.detail
384                   $('#sign-in-modal').modal('hide')
385                   $('#message-modal').modal('show')
386                   return
387                 }
388
389                 document.getElementById('signed-in-status').textContent = 'Signed in.' //`Signed in as ${email}.`
390                 $('#sign-in').hide()
391                 $('#sign-up').hide()
392                 $('#sign-out').show()
393                 sign_in_out(true)
394
395                 document.getElementById('message-modal-message').textContent = `You are now signed in as "${email}".`
396                 $('#sign-in-modal').modal('hide')
397                 $('#message-modal').modal('show')
398               }
399             )
400
401             // sign out button
402             document.getElementById('sign-out').addEventListener(
403               'click',
404               async () => {
405                 try {
406                   await api_account_sign_out()
407                 }
408                 catch (error) {
409                   let problem =
410                     error instanceof Problem ?
411                       error :
412                       new Problem(
413                         // title
414                         'Bad request',
415                         // details
416                         (error.stack || error.message).toString()
417                         // status
418                         400
419                       )
420
421                   document.getElementById('message-modal-message').textContent = problem.detail
422                   $('#sign-in-modal').modal('hide')
423                   $('#message-modal').modal('show')
424                   return
425                 }
426
427                 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
428                 $('#sign-in').show()
429                 $('#sign-up').show()
430                 $('#sign-out').hide()
431                 sign_in_out(false)
432
433                 document.getElementById('message-modal-message').textContent = `You are now signed out.`
434                 $('#sign-in-modal').modal('hide')
435                 $('#message-modal').modal('show')
436               }
437             )
438
439             // feedback form
440             document.getElementById('give-feedback').addEventListener(
441               'click',
442               () => {
443                 $('#feedback-message').text('')
444                 $('#feedback-modal').modal('show')
445                 return false
446               }
447             )
448
449             $('#feedback-modal').on(
450               'shown.bs.modal',
451               () => {
452                 $('#feedback-message').focus()
453               }
454             )
455
456             document.getElementById('feedback-submit').addEventListener(
457               'click',
458               async () => {
459                 try {
460                   await api_feedback(
461                     location.href,
462                     document.getElementById('feedback-message').value.slice(0, 65536)
463                   )
464                 }
465                 catch (error) {
466                   let problem =
467                     error instanceof Problem ?
468                       error :
469                       new Problem(
470                         // title
471                         'Bad request',
472                         // details
473                         (error.stack || error.message).toString()
474                         // status
475                         400
476                       )
477
478                   document.getElementById('message-modal-message').textContent = problem.detail
479                   $('#feedback-modal').modal('hide')
480                   $('#message-modal').modal('show')
481                   return
482                 }
483
484                 document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
485                 $('#feedback-modal').modal('hide')
486                 $('#message-modal').modal('show')
487               }
488             )
489           }
490         )
491       }
492
493       await scripts(_out)
494     }
495   )
496 }