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