More comprehensive system for displaying icons vertically centred within text
[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#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#sign-in(href="#" style="display: none;") {'Sign in'}
76                 else
77                   a#sign-in(href="#") {'Sign in'}
78                 ' '
79                 if (signed_in_as !== undefined)
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 !== undefined)
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                       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#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#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="sign-in-email") {'Email'}
230                     input.form-control#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="sign-in-password") {'Password'}
238                     input.form-control#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#sign-in-submit(type="button") {
258                 'Sign in'
259               }
260             }
261           }
262         }
263       }
264
265       div#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="feedback-message") {'Message'}
279                     textarea.form-control#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#feedback-submit(type="button") {
289                 'Submit'
290               }
291             }
292           }
293         }
294       }
295
296       div#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#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             // sign in form
326             document.getElementById('sign-in').addEventListener(
327               'click',
328               () => {
329                 document.getElementById('sign-in-email').value = ''
330                 document.getElementById('sign-in-password').value = ''
331                 $('#sign-in-modal').modal('show')
332               }
333             )
334
335             $('#sign-in-modal').on(
336               'shown.bs.modal',
337               () => {
338                 console.log('bloo')
339                 $('#sign-in-email').focus()
340               }
341             )
342
343             document.getElementById('sign-in-submit').addEventListener(
344               'click',
345               async () => {
346                 let email
347                 try {
348                   email = document.getElementById('sign-in-email').value.slice(0, 256).toLowerCase()
349                   await api_call(
350                     '/api/account/sign_in.json',
351                     email,
352                     document.getElementById('sign-in-password').value.slice(0, 256)
353                   )
354                 }
355                 catch (error) {
356                   let problem = Problem.from(error)
357
358                   if (problem.title === 'Email not yet verified') {
359                     location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
360                     return
361                   }
362
363                   document.getElementById('message-modal-message').textContent = problem.detail
364                   $('#sign-in-modal').modal('hide')
365                   $('#message-modal').modal('show')
366                   return
367                 }
368
369                 document.getElementById('signed-in-status').textContent = 'Signed in.' //`Signed in as ${email}.`
370                 $('#sign-in').hide()
371                 $('#sign-up').hide()
372                 $('#sign-out').show()
373                 sign_in_out(true)
374
375                 document.getElementById('message-modal-message').textContent = `You are now signed in as "${email}".`
376                 $('#sign-in-modal').modal('hide')
377                 $('#message-modal').modal('show')
378               }
379             )
380
381             // sign out button
382             document.getElementById('sign-out').addEventListener(
383               'click',
384               async () => {
385                 try {
386                   await api_call(
387                     '/api/account/sign_out.json'
388                   )
389                 }
390                 catch (error) {
391                   let problem = Problem.from(error)
392
393                   document.getElementById('message-modal-message').textContent = problem.detail
394                   $('#sign-in-modal').modal('hide')
395                   $('#message-modal').modal('show')
396                   return
397                 }
398
399                 document.getElementById('signed-in-status').textContent = 'Browsing as guest.'
400                 $('#sign-in').show()
401                 $('#sign-up').show()
402                 $('#sign-out').hide()
403                 sign_in_out(false)
404
405                 document.getElementById('message-modal-message').textContent = `You are now signed out.`
406                 $('#sign-in-modal').modal('hide')
407                 $('#message-modal').modal('show')
408               }
409             )
410
411             // feedback form
412             document.getElementById('give-feedback').addEventListener(
413               'click',
414               () => {
415                 $('#feedback-message').text('')
416                 $('#feedback-modal').modal('show')
417                 return false
418               }
419             )
420
421             $('#feedback-modal').on(
422               'shown.bs.modal',
423               () => {
424                 $('#feedback-message').focus()
425               }
426             )
427
428             document.getElementById('feedback-submit').addEventListener(
429               'click',
430               async () => {
431                 try {
432                   await api_call(
433                     '/api/feedback.json',
434                     location.href,
435                     document.getElementById('feedback-message').value.slice(0, 65536)
436                   )
437                 }
438                 catch (error) {
439                   let problem = Problem.from(error)
440
441                   document.getElementById('message-modal-message').textContent = problem.detail
442                   $('#feedback-modal').modal('hide')
443                   $('#message-modal').modal('show')
444                   return
445                 }
446
447                 document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
448                 $('#feedback-modal').modal('hide')
449                 $('#message-modal').modal('show')
450               }
451             )
452           }
453         )
454       }
455
456       await scripts(_out)
457     }
458   )
459 }