9f200e955974753ee8856175b16c24ede00d329d
[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.Transaction()
17   let signed_in_as
18   try {
19     let session = await get_session(env, transaction)
20     signed_in_as = session.get_json('signed_in_as', null)
21   }
22   finally {
23     transaction.rollback()
24   }
25
26   await page(
27     env,
28     // head
29     async _out => {
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)
34
35       title {
36         let transaction = await env.site.database.Transaction()
37         try {
38           let globals = await (await transaction.get({})).get('globals')
39           ;`${await globals.get_json('site_title')}: ${
40             dir.length === 0 ?
41               'Home' :
42               menu.entries[menu.index[dir]].name
43             }`
44         }
45         finally {
46           transaction.rollback()
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 {
213             let transaction = await env.site.database.Transaction()
214             try {
215               let globals = await (await transaction.get({})).get('globals')
216               ;`Copyright © ${new XDate().getUTCFullYear()} ${await globals.get('copyright')}.`
217             }
218             finally {
219               transaction.rollback()
220             }
221           }
222         }
223       }
224
225       // hidden part
226       div#sign-in-modal.modal.fade(role="dialog") {
227         div.modal-dialog {
228           div.modal-content {
229             div.modal-header {
230               span.h4.modal-title {'Sign in'}
231             }
232             div.modal-body {
233               div.row {
234                 div.col-md-12 {
235                   div.form-group {
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) {}
238                   }
239                 }
240               }
241               div.row {
242                 div.col-md-12 {
243                   div.form-group {
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) {}
246                   }
247                 }
248               }
249
250               p.mt-2 {
251                 'No account yet? '
252                 a(href="/my_account/sign_up/index.html") {'Sign up'}
253               }
254
255               p {
256                 'Forgot password? '
257                 a(href="/my_account/password_reset/index.html") {'Password reset'}
258               }
259             }
260             div.modal-footer {
261               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
262                 'Cancel'
263               }
264               button.btn.btn-primary#sign-in-submit(type="button") {
265                 'Sign in'
266               }
267             }
268           }
269         }
270       }
271
272       div#feedback-modal.modal.fade(role="dialog") {
273         div.modal-dialog {
274           div.modal-content {
275             div.modal-header {
276               span.h4.modal-title {'Give feedback'}
277             }
278             div.modal-body {
279               p {
280                 'Did you notice something not quite right, or just want to share your impression of this page?'
281               }
282               div.row {
283                 div.col-md-12 {
284                   div.form-group {
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) {}
287                   }
288                 }
289               }
290             }
291             div.modal-footer {
292               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
293                 'Cancel'
294               }
295               button.btn.btn-primary#feedback-submit(type="button") {
296                 'Submit'
297               }
298             }
299           }
300         }
301       }
302
303       div#message-modal.modal.fade(role="dialog") {
304         div.modal-dialog {
305           div.modal-content {
306             div.modal-header {
307               span.h4.modal-title {'Message'}
308             }
309             div.modal-body#message-modal-message {
310             }
311             div.modal-footer {
312               button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
313                 'Close'
314               }
315             }
316           }
317         }
318       }
319     },
320     // scripts
321     async _out => {
322       script(src="/js/api_call.js") {}
323
324       script {
325         let api_account_sign_in = async (...args) => api_call(
326           '/api/account/sign_in.json',
327           ...args
328         )
329         let api_account_sign_out = async (...args) => api_call(
330           '/api/account/sign_out.json',
331           ...args
332         )
333         let api_feedback = async (...args) => api_call(
334           '/api/feedback.json',
335           ...args
336         )
337
338         // this function can be overridden in a further script
339         function sign_in_out(status) {
340         }
341
342         document.addEventListener(
343           'DOMContentLoaded',
344           () => {
345             // sign in form
346             document.getElementById('sign-in').addEventListener(
347               'click',
348               () => {
349                 document.getElementById('sign-in-email').value = ''
350                 document.getElementById('sign-in-password').value = ''
351                 $('#sign-in-modal').modal('show')
352               }
353             )
354
355             $('#sign-in-modal').on(
356               'shown.bs.modal',
357               () => {
358                 console.log('bloo')
359                 $('#sign-in-email').focus()
360               }
361             )
362
363             document.getElementById('sign-in-submit').addEventListener(
364               'click',
365               async () => {
366                 let email
367                 try {
368                   email = document.getElementById('sign-in-email').value.slice(0, 256).toLowerCase()
369                   await api_account_sign_in(
370                     email,
371                     document.getElementById('sign-in-password').value.slice(0, 256)
372                   )
373                 }
374                 catch (error) {
375                   let problem =
376                     error instanceof Problem ?
377                       error :
378                       new Problem(
379                         // title
380                         'Bad request',
381                         // details
382                         (error.stack || error.message).toString()
383                         // status
384                         400
385                       )
386
387                   if (problem.title === 'Email not yet verified') {
388                     location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
389                     return
390                   }
391
392                   document.getElementById('message-modal-message').textContent = problem.detail
393                   $('#sign-in-modal').modal('hide')
394                   $('#message-modal').modal('show')
395                   return
396                 }
397
398                 document.getElementById('signed-in-status').textContent = 'Signed in.' //`Signed in as ${email}.`
399                 $('#sign-in').hide()
400                 $('#sign-up').hide()
401                 $('#sign-out').show()
402                 sign_in_out(true)
403
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')
407               }
408             )
409
410             // sign out button
411             document.getElementById('sign-out').addEventListener(
412               'click',
413               async () => {
414                 try {
415                   await api_account_sign_out()
416                 }
417                 catch (error) {
418                   let problem =
419                     error instanceof Problem ?
420                       error :
421                       new Problem(
422                         // title
423                         'Bad request',
424                         // details
425                         (error.stack || error.message).toString()
426                         // status
427                         400
428                       )
429
430                   document.getElementById('message-modal-message').textContent = problem.detail
431                   $('#sign-in-modal').modal('hide')
432                   $('#message-modal').modal('show')
433                   return
434                 }
435
436                 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
437                 $('#sign-in').show()
438                 $('#sign-up').show()
439                 $('#sign-out').hide()
440                 sign_in_out(false)
441
442                 document.getElementById('message-modal-message').textContent = `You are now signed out.`
443                 $('#sign-in-modal').modal('hide')
444                 $('#message-modal').modal('show')
445               }
446             )
447
448             // feedback form
449             document.getElementById('give-feedback').addEventListener(
450               'click',
451               () => {
452                 $('#feedback-message').text('')
453                 $('#feedback-modal').modal('show')
454                 return false
455               }
456             )
457
458             $('#feedback-modal').on(
459               'shown.bs.modal',
460               () => {
461                 $('#feedback-message').focus()
462               }
463             )
464
465             document.getElementById('feedback-submit').addEventListener(
466               'click',
467               async () => {
468                 try {
469                   await api_feedback(
470                     location.href,
471                     document.getElementById('feedback-message').value.slice(0, 65536)
472                   )
473                 }
474                 catch (error) {
475                   let problem =
476                     error instanceof Problem ?
477                       error :
478                       new Problem(
479                         // title
480                         'Bad request',
481                         // details
482                         (error.stack || error.message).toString()
483                         // status
484                         400
485                       )
486
487                   document.getElementById('message-modal-message').textContent = problem.detail
488                   $('#feedback-modal').modal('hide')
489                   $('#message-modal').modal('show')
490                   return
491                 }
492
493                 document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
494                 $('#feedback-modal').modal('hide')
495                 $('#message-modal').modal('show')
496               }
497             )
498           }
499         )
500       }
501
502       await scripts(_out)
503     }
504   )
505 }