Add /api/sign_(in|out).json endpoints, uncomment sign in/out logic in navbar and...
[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                   document.getElementById('message-modal-message').textContent = error.message
364                   $('#sign-in-modal').modal('hide')
365                   $('#message-modal').modal('show')
366                   return
367                 }
368
369                 document.getElementById('signed-in-status').textContent = `Signed in as ${email}.`
370                 $('#sign-in').hide()
371                 $('#sign-out').show()
372                 sign_in_out(true)
373
374                 document.getElementById('message-modal-message').textContent = `You are now signed in as "${email}".`
375                 $('#sign-in-modal').modal('hide')
376                 $('#message-modal').modal('show')
377               }
378             )
379
380             // sign out button
381             document.getElementById('sign-out').addEventListener(
382               'click',
383               async () => {
384                 try {
385                   await sign_out()
386                 }
387                 catch (error) {
388                   document.getElementById('message-modal-message').textContent = error.message
389                   $('#sign-in-modal').modal('hide')
390                   $('#message-modal').modal('show')
391                   return
392                 }
393
394                 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
395                 $('#sign-in').show()
396                 $('#sign-out').hide()
397                 sign_in_out(false)
398
399                 document.getElementById('message-modal-message').textContent = `You are now signed out.`
400                 $('#sign-in-modal').modal('hide')
401                 $('#message-modal').modal('show')
402               }
403             )
404
405             // feedback form
406             document.getElementById('give-feedback').addEventListener(
407               'click',
408               () => {
409                 $('#feedback-form-message').text('')
410                 $('#feedback-modal').modal('show')
411                 return false
412               }
413             )
414
415             $('#feedback-modal').on(
416               'shown.bs.modal',
417               () => {
418                 $('#feedback-form-message').focus()
419               }
420             )
421
422             $(document).on(
423               'submit',
424               '#feedback-form',
425               e => {
426                 e.preventDefault()
427                 $.ajax(
428                   {
429                     url: '/api/feedback.html',
430                     type: 'POST',
431                     data: {
432                       page: window.location.href,
433                       message: $('#feedback-form-message').val()
434                     },
435                     success: (data, textStatus, jqXHR) => {
436                       $('#feedback-modal').modal('hide')
437                       document.getElementById('message-modal-message').textContent = data
438                       $('#message-modal-message').text(data)
439                       $('#message-modal').modal('show')
440                     },
441                     error: (jqXHR, textStatus, errorThrown) => {
442                       $('#feedback-modal').modal('hide')
443                       document.getElementById('message-modal-message').textContent = errorThrown
444                       $('#message-modal').modal('show')
445                     }
446                   }
447                 )
448               }
449             )
450           }
451         )
452       }
453
454       await scripts(_out)
455     }
456   )
457 }