Rename /_lib/navbar.jst to /_lib/sidebar.jst, reinstate /_lib/navbar.jst from NDCODE... master
authorNick Downing <nick@ndcode.org>
Fri, 11 Feb 2022 13:54:10 +0000 (00:54 +1100)
committerNick Downing <nick@ndcode.org>
Sat, 12 Feb 2022 01:56:15 +0000 (12:56 +1100)
18 files changed:
_lib/blog_post.jst
_lib/breadcrumbs.jst
_lib/navbar.jst
_lib/sidebar.jst [new file with mode: 0644]
blog/20220202/index.html.jst
blog/20220210/index.html.jst
blog/index.html.jst
contact/index.html.jst
css/bootstrap/_custom.scss
css/bootstrap/_variables.scss
index.html.jst
my_account/index.html.jst
my_account/password_reset/index.html.jst
my_account/send_verification_email/index.html.jst
my_account/sign_up/index.html.jst
my_account/verify_email/index.html.jst
my_account/verify_password/index.html.jst
search/index.html.jst

index 9e16a01..119b1ed 100644 (file)
@@ -3,9 +3,9 @@ let assert = require('assert')
 return async (env, head, body, scripts) => {
   //let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
   let get_navigation = await _require('/_lib/get_navigation.jst')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
-  await navbar(
+  await sidebar(
     env,
     head,
     // body
@@ -28,7 +28,7 @@ return async (env, head, body, scripts) => {
           }
         }
 
-        h3.mb-0 {
+        h3 {
           `${await p.get_json('description')}—by ${await p.get_json('author')}`
         }
       }
index e87597d..754a211 100644 (file)
@@ -7,7 +7,7 @@ return async (env, _out) => {
   let component_titles = env.component_titles
 
   // present component_titles as breadcrumbs, except last one as text
-  h2.py-2 {
+  h2.mt-3 {
     for (let i = 0; i < component_names.length; ++i) {
       a.h4(
         href=
index 0116d12..8988e57 100644 (file)
@@ -5,11 +5,10 @@ let XDate = require('xdate')
 let arrays_equal =
   (a, b) =>
     a.length === b.length && a.every((value, index) => value === b[index])
+
 return async (env, head, body, scripts) => {
   //let cart = await _require('/online_store/cart.jst')
   let fa_arrow_circle_left = await env.site.get_min_svg('/_svg/fa_arrow-circle-left.svg')
-  let fa_bars = await env.site.get_min_svg('/_svg/fa_bars.svg')
   let fa_times_circle = await env.site.get_min_svg('/_svg/fa_times-circle.svg')
   let fa_envelope = await env.site.get_min_svg('/_svg/fa_envelope.svg')
   let fa_unlock_alt = await env.site.get_min_svg('/_svg/fa_unlock-alt.svg')
@@ -18,7 +17,7 @@ return async (env, head, body, scripts) => {
   //let icon_cart_small = await env.site.get_min_svg('/_svg/icon_cart_small.svg')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   //let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let avatar_maker = await env.site.get_min_svg('/_svg/AvatarMaker.svg')
+  let logo_large = await env.site.get_min_svg('/_svg/logo_large.svg')
   let page = await _require('/_lib/page.jst')
 
   // initialize env.cart
@@ -128,127 +127,145 @@ return async (env, head, body, scripts) => {
       let index = env.parsed_url.pathname.indexOf('/', 1)
       let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
 
-      div.container-fluid {
-        div.row {
-          div.col-md.sidebar-outer.sidebar-outer-collapsed#navbar-sidebar-outer {
-            nav.sidebar-inner.d-flex.flex-column#navbar-sidebar-inner {
-              div.mb-4 {
-                div(style="width: 128px; height: 128px;") {
-                  _out.push(avatar_maker)
-                }
-                b.h1 {
-                  `${site_title}`
-                }
+      div.scrollbar-fix-outer {
+        div.scrollbar-fix-inner {
+          div.container {
+            div.row.align-items-center.py-3 {
+              div.col-sm-8 {
+                _out.push(logo_large)
               }
-
-              div.mb-2 {
-                span#navbar-signed-in-status {
+              div.'col-sm-4' {
+                div.'mb-1'.text-right {
+                  span#navbar-signed-in-status {
+                    if (signed_in_as !== undefined)
+                      'Signed in.' //`Signed in as ${signed_in_as}.`
+                    else
+                      'Browsing as guest.'
+                  }
+                  ' '
+                  if (signed_in_as !== undefined)
+                    a#navbar-sign-in(href="#" hidden) {'Sign in'}
+                  else
+                    a#navbar-sign-in(href="#") {'Sign in'}
+                  ' '
+                  if (signed_in_as !== undefined)
+                    a#navbar-sign-up(href="/my_account/sign_up/index.html" hidden) {'Sign up'}
+                  else
+                    a#navbar-sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
+                  ' '
                   if (signed_in_as !== undefined)
-                    'Signed in.'
+                    a#navbar-sign-out(href="#") {'Sign out'}
                   else
-                    'Signed out.'
+                    a#navbar-sign-out(href="#" hidden) {'Sign out'}
                 }
-                ' '
-                if (signed_in_as !== undefined)
-                  a#navbar-sign-in(href="#" hidden) {'Sign in'}
-                else
-                  a#navbar-sign-in(href="#") {'Sign in'}
-                ' '
-                if (signed_in_as !== undefined)
-                  a#navbar-sign-up(href="/my_account/sign_up/index.html" hidden) {'Sign up'}
-                else
-                  a#navbar-sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
-                ' '
-                if (signed_in_as !== undefined)
-                  a#navbar-sign-out(href="#") {'Sign out'}
-                else
-                  a#navbar-sign-out(href="#" hidden) {'Sign out'}
-              }
 
-              form.mb-4(action="/search/index.html") {
-                div.input-group {
-                  input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
-                  div.input-group-append {
-                    button.btn.btn-outline-secondary#navbar-search-button(type="submit") {
-                      div.icon24-outer {
-                        div.icon24-inner {_out.push(fa_search)}
+                form(action="/search/index.html") {
+                  div.input-group {
+                    input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
+                    div.input-group-append {
+                      button.btn.btn-outline-secondary#navbar-search-button(type="submit") {
+                        div.icon24-outer {
+                          div.icon24-inner {_out.push(fa_search)}
+                        }
                       }
                     }
                   }
                 }
               }
 
-              // the active entry in the navbar bar is based on which top-level
-              // page we are under, even if we are not directly on that page
-              // but one of its children, this may be unexpected as the active
-              // entry does not highlight on hover, but you can still click it;
-              // we determine here the path to the corresponding top-level page
-              let component_prefix = component_names.slice(0, 1)
-
-              for (let i = 0; i < menu_titles.length; ++i) {
-                // construct path to the top-level page about to be described
-                let menu_prefix =
-                  i === 0 ? [] : [menu_names[i - 1]]
-                let menu_prefix_path =
-                  menu_prefix.map(name => '/' + name).join('') + '/index.html'
-
-                if (arrays_equal(menu_prefix, component_prefix))
-                  div.nav-item.active {
-                    a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
-                      `${menu_titles[i]}`
-                      span.sr-only {' (current)'}
-                    }
+              //div.'col-sm-1'.vbottom {
+              //  // a nested div is used to avoid hover colour on the padding
+              //  div.nav-li-a(style="text-align: center;") {
+              //    a(href="/online_store/view_cart/index.html") {
+              //      div.cart-icon {
+              //        _out.push(icon_cart_small)
+              //      }
+              //      div.cart-number {
+              //        div.cart-circle {
+              //          `${(env.cart.items || []).length}`
+              //        }
+              //      }
+              //    }
+              //  }
+              //}
+            }
+            nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.container-background {
+              //a.navbar-brand(href="#") {'Navbar'}
+              //' '
+              button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation") {
+                span.navbar-toggler-icon {}
+              }
+              div.collapse.navbar-collapse#navbarSupportedContent {
+                ul.navbar-nav.mr-auto {
+                  // the active entry in the navbar bar is based on which top-level
+                  // page we are under, even if we are not directly on that page
+                  // but one of its children, this may be unexpected as the active
+                  // entry does not highlight on hover, but you can still click it;
+                  // we determine here the path to the corresponding top-level page
+                  let component_prefix = component_names.slice(0, 1)
+
+                  for (let i = 0; i < menu_titles.length; ++i) {
+                    // construct path to the top-level page about to be described
+                    let menu_prefix =
+                      i == 0 ? [] : [menu_names[i - 1]]
+                    let menu_prefix_path =
+                      menu_prefix.map(name => '/' + name).join('') + '/index.html'
+
+                    if (arrays_equal(menu_prefix, component_prefix))
+                      li.nav-item.active {
+                        a.nav-link(href=menu_prefix_path) {
+                          `${menu_titles[i]}`
+                          span.sr-only {' (current)'}
+                        }
+                      }
+                    else
+                      li.nav-item {
+                        a.nav-link(href=menu_prefix_path) {
+                          `${menu_titles[i]}`
+                        }
+                      }
                   }
-                else
-                  div.nav-item {
-                    a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
-                      `${menu_titles[i]}`
-                    }
+                  //li.nav-item.dropdown {
+                  //  a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
+                  //    'Dropdown'
+                  //  }
+                  //  div.dropdown-menu(aria-labelledby="navbarDropdown") {
+                  //    a.dropdown-item(href="#") {
+                  //      'Action'
+                  //    }
+                  //    ' '
+                  //    a.dropdown-item(href="#") {
+                  //      'Another action'
+                  //    }
+                  //    div.dropdown-divider {}
+                  //    a.dropdown-item(href="#") {
+                  //      'Something else here'
+                  //    }
+                  //  }
+                  //}
+                  //li.nav-item {
+                  //  a.nav-link.disabled {
+                  //    'Disabled'
+                  //  }
+                  //}
+                }
+                ul.navbar-nav.ml-auto {
+                  li.nav-item {
+                    a.nav-link#navbar-give-feedback(href="#") {'Give feedback'}
                   }
-              }
-              div.nav-item.mt-auto {
-                a.nav-link.nav-link2.grid-gutter-background#navbar-give-feedback(href="#") {'Give feedback'}
-              }
-            }
-          }
-
-          div.col-md.sidebar-content {
-            // the breadcrumbs have already been determined by navbar.jst, as
-            // the HTML title is similar to the breadcrumbs (but without links)
-            let component_names = env.component_names
-            let component_titles = env.component_titles
-
-            // present component_titles as breadcrumbs, except last one as text
-            h2.page-header.grid-gutter-background.'py-2'.mb-0 {
-              button.btn.btn-outline-secondary.sidebar-toggle.mr-3#navbar-sidebar-toggle {
-                div.icon24-outer(style="top: -1px;") {
-                  div.icon24-inner {_out.push(fa_bars)}
                 }
-                span.sr-only {'Navbar toggle'}
-              }
-              for (let i = 0; i < component_names.length; ++i) {
-                a.h4(
-                  href=
-                    `${
-                      component_names.slice(0, i).map(name => '/' + name).join('')
-                    }/index.html`
-                ) {`${component_titles[i]}`}
-                ' '
-                span.h5 {'>'}
-                ' '
               }
-              `${component_titles[component_names.length]}`
             }
 
             await body(_out)
 
-            footer.page-footer.grid-gutter-background.py-5 {
+            footer.page-footer.container-background.py-5 {
               a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
                 img(alt="Creative Commons License" style="border-width:0;" src="/images/by-sa_3.0_88x31.png") {}
               }
               p {
                 'This website is '
-                a(href="https://git.ndcode.org/public/nick_site.git") {
+                a(href="https://git.ndcode.org/public/ndcode_site.git") {
                   'open source'
                 }
                 ' and licensed under a '
@@ -257,11 +274,12 @@ return async (env, head, body, scripts) => {
                 }
                 '.'
               }
-    
+
+              p {'Example code fragments embedded within the text are placed in the public domain unless otherwise noted.'}
+
               p.mb-0 {`Copyright © ${new XDate(env.now).getUTCFullYear()} ${copyright}.`}
             }
           }
-          div.col-md.sidebar-dummy {}
         }
       }
 
@@ -457,9 +475,6 @@ return async (env, head, body, scripts) => {
             let id_navbar_message_modal = document.getElementById('navbar-message-modal')
             let id_navbar_message_modal_message = document.getElementById('navbar-message-modal-message')
             let id_navbar_search_button = document.getElementById('navbar-search-button')
-            let id_navbar_sidebar_inner = document.getElementById('navbar-sidebar-inner')
-            let id_navbar_sidebar_outer = document.getElementById('navbar-sidebar-outer')
-            let id_navbar_sidebar_toggle = document.getElementById('navbar-sidebar-toggle')
             let id_navbar_sign_in = document.getElementById('navbar-sign-in')
             let id_navbar_sign_in_alert = document.getElementById('navbar-sign-in-alert')
             let id_navbar_sign_in_cross = document.getElementById('navbar-sign-in-cross')
@@ -474,6 +489,8 @@ return async (env, head, body, scripts) => {
             let id_navbar_sign_out = document.getElementById('navbar-sign-out')
             let id_navbar_sign_up = document.getElementById('navbar-sign-up')
             let id_navbar_signed_in_status = document.getElementById('navbar-signed-in-status')
+            //let id_navbarDropdown = document.getElementById('navbarDropdown')
+            //let id_navbarSupportedContent = document.getElementById('navbarSupportedContent')
 
             // sign in form
             id_navbar_sign_in.addEventListener(
@@ -728,70 +745,6 @@ return async (env, head, body, scripts) => {
                 $('#navbar-message-modal').modal('show')
               }
             )
-
-            // sidebar
-            let sidebar_outer_computed_style = window.getComputedStyle(
-              id_navbar_sidebar_outer
-            )
-            let sidebar_toggle_computed_style = window.getComputedStyle(
-              id_navbar_sidebar_toggle
-            )
-            let sidebar_is_collapsed =
-              () =>
-                sidebar_toggle_computed_style.display !== 'none' &&
-                  id_navbar_sidebar_outer.classList.contains(
-                    'sidebar-outer-collapsed'
-                  )
-            let sidebar_collapse_update = () => {
-              if (sidebar_outer_computed_style.position === 'sticky') { // md and up
-                id_navbar_sidebar_outer.style.flexBasis =
-                  sidebar_is_collapsed() ?
-                    '0px' :
-                    `${id_navbar_sidebar_inner.clientWidth}px`
-                id_navbar_sidebar_outer.style.removeProperty('height')
-              }
-              else {
-                id_navbar_sidebar_outer.style.height =
-                  sidebar_is_collapsed() ?
-                    '0px' :
-                    `${id_navbar_sidebar_inner.clientHeight}px`
-                id_navbar_sidebar_outer.style.removeProperty('flex-basis')
-              }
-            }
-            window.addEventListener('resize', sidebar_collapse_update)
-            sidebar_collapse_update()
-
-            id_navbar_sidebar_outer.addEventListener(
-              'transitionend',
-              () => {
-                // transitions are only allowed after clicking collapse button,
-                // otherwise they can be triggered by media queries on resize
-                id_navbar_sidebar_outer.style.removeProperty('transition-property')
-              }
-            )
-
-            id_navbar_sidebar_toggle.addEventListener(
-              'click',
-              () => {
-                if (
-                  id_navbar_sidebar_outer.classList.contains(
-                    'sidebar-outer-collapsed'
-                  )
-                )
-                  id_navbar_sidebar_outer.classList.remove(
-                    'sidebar-outer-collapsed'
-                  )
-                else
-                   id_navbar_sidebar_outer.classList.add(
-                    'sidebar-outer-collapsed'
-                  )
-                id_navbar_sidebar_outer.style.transitionProperty =
-                  sidebar_outer_computed_style.position === 'sticky' ?
-                    'flex-basis' : // md and up
-                    'height'
-                sidebar_collapse_update()
-              }
-            )
           }
         )
       }
diff --git a/_lib/sidebar.jst b/_lib/sidebar.jst
new file mode 100644 (file)
index 0000000..5f108e2
--- /dev/null
@@ -0,0 +1,802 @@
+let assert = require('assert')
+let jst_server = (await import('@ndcode/jst_server')).default
+let XDate = require('xdate')
+
+let arrays_equal =
+  (a, b) =>
+    a.length === b.length && a.every((value, index) => value === b[index])
+
+return async (env, head, body, scripts) => {
+  //let cart = await _require('/online_store/cart.jst')
+  let fa_arrow_circle_left = await env.site.get_min_svg('/_svg/fa_arrow-circle-left.svg')
+  let fa_bars = await env.site.get_min_svg('/_svg/fa_bars.svg')
+  let fa_times_circle = await env.site.get_min_svg('/_svg/fa_times-circle.svg')
+  let fa_envelope = await env.site.get_min_svg('/_svg/fa_envelope.svg')
+  let fa_unlock_alt = await env.site.get_min_svg('/_svg/fa_unlock-alt.svg')
+  let fa_search = await env.site.get_min_svg('/_svg/fa_search.svg')
+  let get_session = await _require('/_lib/get_session.jst')
+  //let icon_cart_small = await env.site.get_min_svg('/_svg/icon_cart_small.svg')
+  let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
+  //let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
+  let avatar_maker = await env.site.get_min_svg('/_svg/AvatarMaker.svg')
+  let page = await _require('/_lib/page.jst')
+
+  // initialize env.cart
+  //await cart(env)
+
+  // compute breadcrumbs from directories of the path
+  let component_names = env.parsed_url.pathname.split('/')
+  assert(component_names.length >= 2)
+  assert(component_names[0].length === 0)
+  assert(component_names[component_names.length - 1].length)
+  component_names = component_names.slice(1, -1)
+
+  let transaction = await env.site.database.Transaction()
+  let signed_in_as
+  let site_title, copyright
+  let component_titles // collects breadcrumb titles for current page
+  let menu_names, menu_titles // collects top level of menu for the sidebar
+  let feedback_draft
+  try {
+    let root = await transaction.get()
+
+    let session = await get_session(env, root)
+    signed_in_as = await session.get_json('signed_in_as')
+
+    let globals = await root.get('globals')
+    site_title = await globals.get_json('site_title')
+    copyright = await globals.get_json('copyright')
+
+    let navigation = await root.get('navigation')
+    if (navigation === undefined)
+      throw new jst_server.Problem(
+        'Navigation error',
+        'Please import the navigation tree into the database.',
+        508
+      )
+
+    // this code is taken from get_navigation.jst and instrumented
+    let p = navigation
+    component_titles = [await p.get_json('title')] // Home
+    for (let i = 0; i < component_names.length; ++i) {
+      let children = await p.get('children')
+      p = await children.get(component_names[i])
+      if (navigation === undefined)
+        throw new jst_server.Problem(
+          'Navigation error',
+          `Can't find the path "${
+            component_names.slice(0, i + 1).map(name => '/' + name).join('')
+          }" in the navigation tree.`,
+          508
+        )
+      component_titles.push(await p.get_json('title'))
+    }
+
+    // similar to above but walks the top level laterally (not deeply)
+    menu_names = await navigation.get_json('menu')
+    let children = await navigation.get('children')
+    menu_titles = [await navigation.get_json('title')] // Home
+    for (let i = 0; i < menu_names.length; ++i) {
+      let child = await children.get(menu_names[i])
+      if (child === undefined)
+        throw new jst_server.Problem(
+          'Navigation error',
+          `Can't find the path "/${menu_names[i]}" in the navigation tree.`
+          508
+        )
+      menu_titles.push(await child.get('title'))
+    }
+
+    feedback_draft = await session.get_json('feedback_draft')
+    if (feedback_draft === undefined || env.now >= feedback_draft.expires)
+      feedback_draft = null
+  }
+  finally {
+    transaction.rollback()
+  }
+
+  // save breadcrumbs and their titles for breadcrumbs.jst
+  // note: component_titles.length === component_names.length + 1
+  // component_titles[0] corresponds to /, is 'Home' or similar
+  // component_titles[i] corresponds to component_names[i - 1], i >= 1
+  env.component_names = component_names
+  env.component_titles = component_titles
+
+  // note: menu_titles.length === menu_names.length + 1
+  // menu_titles[0] corresponds to /, is 'Home' or similar
+  // menu_titles[i] corresponds to menu_names[i - 1], i >= 1
+  // (sidebar has Home appearing at same level as its immediate children)
+
+  await page(
+    env,
+    // head
+    async _out => {
+      title {
+        `${site_title}: ${
+          component_titles[
+            component_names.length >= 2 ? 1 : component_names.length
+          ]
+        }`
+      }
+
+      await head(_out)
+    },
+    // body
+    async _out => {
+      // extract top-level directory name
+      assert(env.parsed_url.pathname.slice(0, 1) === '/')
+      let index = env.parsed_url.pathname.indexOf('/', 1)
+      let dir = index === -1 ? '' : env.parsed_url.pathname.slice(1, index)
+
+      div.container-fluid {
+        div.row {
+          div.col-md.sidebar-outer.sidebar-outer-collapsed#sidebar-outer {
+            nav.sidebar-inner.d-flex.flex-column#sidebar-inner {
+              div.mb-4 {
+                div(style="width: 128px; height: 128px;") {
+                  _out.push(avatar_maker)
+                }
+                b.h1 {
+                  `${site_title}`
+                }
+              }
+
+              div.mb-2 {
+                span#sidebar-signed-in-status {
+                  if (signed_in_as !== undefined)
+                    'Signed in.'
+                  else
+                    'Signed out.'
+                }
+                ' '
+                if (signed_in_as !== undefined)
+                  a#sidebar-sign-in(href="#" hidden) {'Sign in'}
+                else
+                  a#sidebar-sign-in(href="#") {'Sign in'}
+                ' '
+                if (signed_in_as !== undefined)
+                  a#sidebar-sign-up(href="/my_account/sign_up/index.html" hidden) {'Sign up'}
+                else
+                  a#sidebar-sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
+                ' '
+                if (signed_in_as !== undefined)
+                  a#sidebar-sign-out(href="#") {'Sign out'}
+                else
+                  a#sidebar-sign-out(href="#" hidden) {'Sign out'}
+              }
+
+              form.mb-4(action="/search/index.html") {
+                div.input-group {
+                  input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
+                  div.input-group-append {
+                    button.btn.btn-outline-secondary#sidebar-search-button(type="submit") {
+                      div.icon24-outer {
+                        div.icon24-inner {_out.push(fa_search)}
+                      }
+                    }
+                  }
+                }
+              }
+
+              // the active entry in the sidebar bar is based on which top-level
+              // page we are under, even if we are not directly on that page
+              // but one of its children, this may be unexpected as the active
+              // entry does not highlight on hover, but you can still click it;
+              // we determine here the path to the corresponding top-level page
+              let component_prefix = component_names.slice(0, 1)
+
+              for (let i = 0; i < menu_titles.length; ++i) {
+                // construct path to the top-level page about to be described
+                let menu_prefix =
+                  i === 0 ? [] : [menu_names[i - 1]]
+                let menu_prefix_path =
+                  menu_prefix.map(name => '/' + name).join('') + '/index.html'
+
+                if (arrays_equal(menu_prefix, component_prefix))
+                  div.nav-item.active {
+                    a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
+                      `${menu_titles[i]}`
+                      span.sr-only {' (current)'}
+                    }
+                  }
+                else
+                  div.nav-item {
+                    a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
+                      `${menu_titles[i]}`
+                    }
+                  }
+              }
+              div.nav-item.mt-auto {
+                a.nav-link.nav-link2.grid-gutter-background#sidebar-give-feedback(href="#") {'Give feedback'}
+              }
+            }
+          }
+
+          div.col-md.sidebar-content {
+            // the breadcrumbs have already been determined by sidebar.jst, as
+            // the HTML title is similar to the breadcrumbs (but without links)
+            let component_names = env.component_names
+            let component_titles = env.component_titles
+
+            // present component_titles as breadcrumbs, except last one as text
+            h2.page-header.grid-gutter-background.'py-2'.mb-0 {
+              button.btn.btn-outline-secondary.sidebar-toggle.mr-3#sidebar-toggle {
+                div.icon24-outer(style="top: -1px;") {
+                  div.icon24-inner {_out.push(fa_bars)}
+                }
+                span.sr-only {'Navbar toggle'}
+              }
+              for (let i = 0; i < component_names.length; ++i) {
+                a.h4(
+                  href=
+                    `${
+                      component_names.slice(0, i).map(name => '/' + name).join('')
+                    }/index.html`
+                ) {`${component_titles[i]}`}
+                ' '
+                span.h5 {'>'}
+                ' '
+              }
+              `${component_titles[component_names.length]}`
+            }
+
+            await body(_out)
+
+            footer.page-footer.grid-gutter-background.py-5 {
+              a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
+                img(alt="Creative Commons License" style="border-width:0;" src="/images/by-sa_3.0_88x31.png") {}
+              }
+              p {
+                'This website is '
+                a(href="https://git.ndcode.org/public/nick_site.git") {
+                  'open source'
+                }
+                ' and licensed under a '
+                a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
+                  'Creative Commons Attribution-ShareAlike 3.0 Unported License'
+                }
+                '.'
+              }
+
+              p.mb-0 {`Copyright © ${new XDate(env.now).getUTCFullYear()} ${copyright}.`}
+            }
+          }
+          div.col-md.sidebar-dummy {}
+        }
+      }
+
+      // hidden part
+      div.modal#sidebar-sign-in-modal(tabindex="-1") {
+        div.modal-dialog {
+          div.modal-content {
+            div.modal-header {
+              span.h4.modal-title {'Sign in'}
+            }
+            div.modal-body {
+              form.mb-2#sidebar-sign-in-form {
+                div.row {
+                  div.col-md-12 {
+                    div.form-group {
+                      label.form-label(for="sidebar-sign-in-email") {'Email'}
+                      input.form-control#sidebar-sign-in-email(type="email" required maxlength=256) {}
+                      div.invalid-feedback {'Please enter your account\'s email address.'}
+                    }
+                  }
+                }
+                div.row {
+                  div.col-md-12 {
+                    div.form-group {
+                      label.form-label(for="sidebar-sign-in-password") {'Password'}
+                      input.form-control#sidebar-sign-in-password(type="password" required minlength=8 maxlength=256) {}
+                      div.invalid-feedback {'Please enter at least 8 characters.'}
+                    }
+                  }
+                }
+              }
+
+              p {
+                'No account yet? '
+                a(href="/my_account/sign_up/index.html") {'Sign up'}
+              }
+
+              p.mb-0 {
+                'Forgot password? '
+                a(href="/my_account/password_reset/index.html") {'Password reset'}
+              }
+
+              div.alert.alert-danger.'mt-3'.mb-0#sidebar-sign-in-alert(hidden) {}
+            }
+            div.modal-footer {
+              button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
+                div.icon24-outer.mr-2 {
+                  div.icon24-inner {_out.push(fa_arrow_circle_left)}
+                }
+                'Back'
+              }
+              button.btn.btn-primary#sidebar-sign-in-sign-in(type="button") {
+                div.icon24-outer.mr-2#sidebar-sign-in-icon {
+                  div.icon24-inner {_out.push(fa_unlock_alt)}
+                }
+                //div.icon24-outer.mr-2#sidebar-sign-in-tick(hidden) {
+                //  div.icon24-inner {_out.push(icon_tick)}
+                //}
+                div.icon24-outer.mr-2#sidebar-sign-in-cross(hidden) {
+                  div.icon24-inner {_out.push(icon_cross)}
+                }
+                div.icon24-outer.mr-2#sidebar-sign-in-spinner(hidden) {
+                  div.icon24-inner {
+                    div.spinner-border.spinner-border-sm(role="status") {}
+                  }
+                }
+                'Sign in'
+              }
+            }
+          }
+        }
+      }
+
+      div.modal#sidebar-feedback-modal(tabindex="-1") {
+        div.modal-dialog {
+          div.modal-content {
+            div.modal-header {
+              span.h4.modal-title {'Give feedback'}
+            }
+            div.modal-body {
+              p {
+                'Did you notice something not quite right, or just want to share your impression of this page?'
+              }
+
+              form#sidebar-feedback-form {
+                div.row {
+                  div.col-md-12 {
+                    div.form-group {
+                      label.form-label(for="sidebar-feedback-message") {'Message'}
+                      textarea.form-control#sidebar-feedback-message(placeholder="I noticed that..." required rows=4 maxlength=65536) {
+                        if (feedback_draft)
+                          `${feedback_draft.message}`
+                      }
+                      div.invalid-feedback {'Please let us have your thoughts.'}
+                    }
+                  }
+                }
+              }
+
+              div.alert.alert-danger.'mt-3'.mb-0#sidebar-feedback-alert(hidden) {}
+            }
+            div.modal-footer {
+              button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
+                div.icon24-outer.mr-2 {
+                  div.icon24-inner {_out.push(fa_arrow_circle_left)}
+                }
+                'Back'
+              }
+              if (feedback_draft)
+                button.btn.btn-primary#sidebar-feedback-send-message(type="button") {
+                  div.icon24-outer.mr-2#sidebar-feedback-icon {
+                    div.icon24-inner {_out.push(fa_envelope)}
+                  }
+                  //div.icon24-outer.mr-2#sidebar-feedback-tick(hidden) {
+                  //  div.icon24-inner {_out.push(icon_tick)}
+                  //}
+                  div.icon24-outer.mr-2#sidebar-feedback-cross(hidden) {
+                    div.icon24-inner {_out.push(icon_cross)}
+                  }
+                  div.icon24-outer.mr-2#sidebar-feedback-spinner(hidden) {
+                    div.icon24-inner {
+                      div.spinner-border.spinner-border-sm(role="status") {}
+                    }
+                  }
+                  'Send message'
+                }
+              else
+                button.btn.btn-primary#sidebar-feedback-send-message(type="button" disabled) {
+                  div.icon24-outer.mr-2#sidebar-feedback-icon {
+                    div.icon24-inner {_out.push(fa_envelope)}
+                  }
+                  //div.icon24-outer.mr-2#sidebar-feedback-tick(hidden) {
+                  //  div.icon24-inner {_out.push(icon_tick)}
+                  //}
+                  div.icon24-outer.mr-2#sidebar-feedback-cross(hidden) {
+                    div.icon24-inner {_out.push(icon_cross)}
+                  }
+                  div.icon24-outer.mr-2#sidebar-feedback-spinner(hidden) {
+                    div.icon24-inner {
+                      div.spinner-border.spinner-border-sm(role="status") {}
+                    }
+                  }
+                  'Send message'
+                }
+            }
+          }
+        }
+      }
+
+      div.modal#sidebar-message-modal(tabindex="-1") {
+        div.modal-dialog {
+          div.modal-content {
+            div.modal-header {
+              span.h4.modal-title {'Message'}
+            }
+            div.modal-body#sidebar-message-modal-message {
+            }
+            div.modal-footer {
+              button.btn.btn-outline-secondary(type="button" data-dismiss="modal") {
+                div.icon24-outer.mr-2 {
+                  div.icon24-inner {_out.push(fa_times_circle)}
+                }
+                'Close'
+              }
+            }
+          }
+        }
+      }
+    },
+    // scripts
+    async _out => {
+      script(src="/js/utils.js") {}
+
+      script {
+        // this function can be overridden in a further script
+        function sign_in_out(status) {
+          return false
+        }
+
+        document.addEventListener(
+          'DOMContentLoaded',
+          () => {
+            let id_sidebar_feedback_alert = document.getElementById('sidebar-feedback-alert')
+            let id_sidebar_feedback_cross = document.getElementById('sidebar-feedback-cross')
+            let id_sidebar_feedback_form = document.getElementById('sidebar-feedback-form')
+            let id_sidebar_feedback_icon = document.getElementById('sidebar-feedback-icon')
+            let id_sidebar_feedback_message = document.getElementById('sidebar-feedback-message')
+            let id_sidebar_feedback_modal = document.getElementById('sidebar-feedback-modal')
+            let id_sidebar_feedback_send_message = document.getElementById('sidebar-feedback-send-message')
+            let id_sidebar_feedback_spinner = document.getElementById('sidebar-feedback-spinner')
+            let id_sidebar_feedback_tick = document.getElementById('sidebar-feedback-tick')
+            let id_sidebar_give_feedback = document.getElementById('sidebar-give-feedback')
+            let id_sidebar_inner = document.getElementById('sidebar-inner')
+            let id_sidebar_message_modal = document.getElementById('sidebar-message-modal')
+            let id_sidebar_message_modal_message = document.getElementById('sidebar-message-modal-message')
+            let id_sidebar_outer = document.getElementById('sidebar-outer')
+            let id_sidebar_search_button = document.getElementById('sidebar-search-button')
+            let id_sidebar_sign_in = document.getElementById('sidebar-sign-in')
+            let id_sidebar_sign_in_alert = document.getElementById('sidebar-sign-in-alert')
+            let id_sidebar_sign_in_cross = document.getElementById('sidebar-sign-in-cross')
+            let id_sidebar_sign_in_email = document.getElementById('sidebar-sign-in-email')
+            let id_sidebar_sign_in_form = document.getElementById('sidebar-sign-in-form')
+            let id_sidebar_sign_in_icon = document.getElementById('sidebar-sign-in-icon')
+            let id_sidebar_sign_in_modal = document.getElementById('sidebar-sign-in-modal')
+            let id_sidebar_sign_in_password = document.getElementById('sidebar-sign-in-password')
+            let id_sidebar_sign_in_sign_in = document.getElementById('sidebar-sign-in-sign-in')
+            let id_sidebar_sign_in_spinner = document.getElementById('sidebar-sign-in-spinner')
+            let id_sidebar_sign_in_tick = document.getElementById('sidebar-sign-in-tick')
+            let id_sidebar_sign_out = document.getElementById('sidebar-sign-out')
+            let id_sidebar_sign_up = document.getElementById('sidebar-sign-up')
+            let id_sidebar_signed_in_status = document.getElementById('sidebar-signed-in-status')
+            let id_sidebar_toggle = document.getElementById('sidebar-toggle')
+
+            // sign in form
+            id_sidebar_sign_in.addEventListener(
+              'click',
+              () => {
+                id_sidebar_sign_in_email.value = ''
+                id_sidebar_sign_in_password.value = ''
+                id_sidebar_sign_in_sign_in.disabled = true
+                $('#sidebar-sign-in-modal').modal('show')
+              }
+            )
+
+            $('#sidebar-sign-in-modal').on(
+              'shown.bs.modal',
+              () => {id_sidebar_sign_in_email.focus()}
+            )
+
+            let sign_in_edited = () => {
+              id_sidebar_sign_in_sign_in.disabled =
+                id_sidebar_sign_in_email.value.length === 0 &&
+                  id_sidebar_sign_in_password.value.length === 0
+              id_sidebar_sign_in_icon.hidden = false
+              //id_sidebar_sign_in_tick.hidden = true
+              id_sidebar_sign_in_cross.hidden = true
+              id_sidebar_sign_in_spinner.hidden = true
+              id_sidebar_sign_in_alert.hidden = true
+            }
+
+            id_sidebar_sign_in_email.addEventListener(
+              'input',
+              sign_in_edited
+            )
+            id_sidebar_sign_in_password.addEventListener(
+              'input',
+              sign_in_edited
+            )
+
+            id_sidebar_sign_in_sign_in.addEventListener(
+              'click',
+              async () => {
+                id_sidebar_sign_in_icon.hidden = false
+                //id_sidebar_sign_in_tick.hidden = true
+                id_sidebar_sign_in_cross.hidden = true
+                id_sidebar_sign_in_spinner.hidden = true
+                // the below causes an ugly flicker, so just keep the alert
+                //id_sidebar_sign_in_alert.hidden = true
+
+                if (!id_sidebar_sign_in_form.checkValidity()) {
+                  id_sidebar_sign_in_form.classList.add('was-validated');
+
+                  id_sidebar_sign_in_icon.hidden = true
+                  id_sidebar_sign_in_cross.hidden = false
+                  return
+                }
+                id_sidebar_sign_in_form.classList.remove('was-validated');
+
+                let email = id_sidebar_sign_in_email.value.slice(0, 256).toLowerCase()
+
+                id_sidebar_sign_in_icon.hidden = true
+                id_sidebar_sign_in_spinner.hidden = false
+                try {
+                  await api_call(
+                    '/api/account/sign_in.json',
+                    email,
+                    id_sidebar_sign_in_password.value.slice(0, 256)
+                  )
+                }
+                catch (error) {
+                  let problem = Problem.from(error)
+
+                  if (problem.title === 'Email not yet verified') {
+                    location.href = `/my_account/send_verification_email?email=${encodeURIComponent(email)}`
+                    return
+                  }
+
+                  id_sidebar_sign_in_cross.hidden = false
+                  id_sidebar_sign_in_spinner.hidden = true
+
+                  id_sidebar_sign_in_alert.textContent = problem.detail
+                  //id_sidebar_sign_in_alert.classList.remove('alert-success')
+                  //id_sidebar_sign_in_alert.classList.add('alert-danger')
+                  id_sidebar_sign_in_alert.hidden = false
+                  return
+                }
+                //id_sidebar_sign_in_tick.hidden = false
+                //id_sidebar_sign_in_spinner.hidden = true
+                //id_sidebar_sign_in_alert.textContent = `You are now signed in as "${email}".`
+                //id_sidebar_sign_in_alert.classList.add('alert-success')
+                //id_sidebar_sign_in_alert.classList.remove('alert-danger')
+                //id_sidebar_sign_in_alert.hidden = false
+
+                if (sign_in_out(true))
+                  // if location has been changed, leave the spinner and do
+                  // not show status/dialog, as it causes an annoying flicker
+                  return
+
+                id_sidebar_signed_in_status.textContent = 'Signed in.'
+                id_sidebar_sign_in.hidden = true
+                id_sidebar_sign_up.hidden = true
+                id_sidebar_sign_out.hidden = false
+
+                id_sidebar_sign_in_icon.hidden = false
+                id_sidebar_sign_in_spinner.hidden = true
+                id_sidebar_sign_in_alert.hidden = true
+                id_sidebar_message_modal_message.textContent = `You are now signed in as "${email}".`
+                $('#sidebar-sign-in-modal').modal('hide')
+                $('#sidebar-message-modal').modal('show')
+              }
+            )
+
+            // sign out button
+            id_sidebar_sign_out.addEventListener(
+              'click',
+              async () => {
+                try {
+                  await api_call(
+                    '/api/account/sign_out.json'
+                  )
+                }
+                catch (error) {
+                  let problem = Problem.from(error)
+
+                  id_sidebar_message_modal_message.textContent = problem.detail
+                  $('#sidebar-sign-in-modal').modal('hide')
+                  $('#sidebar-message-modal').modal('show')
+                  return
+                }
+
+                if (sign_in_out(false))
+                  // if location has been changed, leave the spinner and do
+                  // not show status/dialog, as it causes an annoying flicker
+                  return
+
+                id_sidebar_signed_in_status.textContent = 'Signed out.'
+                id_sidebar_sign_in.hidden = false
+                id_sidebar_sign_up.hidden = false
+                id_sidebar_sign_out.hidden = true
+
+                id_sidebar_message_modal_message.textContent = `You are now signed out.`
+                $('#sidebar-sign-in-modal').modal('hide')
+                $('#sidebar-message-modal').modal('show')
+              }
+            )
+
+            // feedback form
+            id_sidebar_give_feedback.addEventListener(
+              'click',
+              () => {
+                // hack to move cursor to end of textarea
+                let temp = id_sidebar_feedback_message.value
+                id_sidebar_feedback_message.value = ''
+                id_sidebar_feedback_message.value = temp
+
+                $('#sidebar-feedback-modal').modal('show')
+                return false
+              }
+            )
+
+            $('#sidebar-feedback-modal').on(
+              'shown.bs.modal',
+              () => {id_sidebar_feedback_message.focus()}
+            )
+
+            let feedback_input_semaphore = new BinarySemaphore(false)
+            ;(
+              async () => {
+                while (true) {
+                  await feedback_input_semaphore.acquire()
+                  await new Promise(resolve => setTimeout(resolve, 3000))
+                  feedback_input_semaphore.try_acquire()
+                  await api_call(
+                    '/api/feedback/set_draft.json',
+                    id_sidebar_feedback_message.value.length === 0 ?
+                      null :
+                      {
+                        message: id_sidebar_feedback_message.value.slice(0, 65536)
+                      }
+                  )
+                }
+              }
+            )() // ignore returned promise (start thread)
+
+            let feedback_edited = () => {
+              feedback_input_semaphore.release()
+
+              id_sidebar_feedback_send_message.disabled =
+                id_sidebar_feedback_message.value.length === 0
+              id_sidebar_feedback_icon.hidden = false
+              //id_sidebar_feedback_tick.hidden = true
+              id_sidebar_feedback_cross.hidden = true
+              id_sidebar_feedback_spinner.hidden = true
+              id_sidebar_feedback_alert.hidden = true
+            }
+
+            id_sidebar_feedback_message.addEventListener(
+              'input',
+              feedback_edited
+            )
+
+            id_sidebar_feedback_send_message.addEventListener(
+              'click',
+              async () => {
+                id_sidebar_feedback_icon.hidden = false
+                //id_sidebar_feedback_tick.hidden = true
+                id_sidebar_feedback_cross.hidden = true
+                id_sidebar_feedback_spinner.hidden = true
+                // the below causes an ugly flicker, so just keep the alert
+                //id_sidebar_feedback_alert.hidden = true
+
+                if (!id_sidebar_feedback_form.checkValidity()) {
+                  id_sidebar_feedback_form.classList.add('was-validated');
+
+                  id_sidebar_feedback_icon.hidden = true
+                  id_sidebar_feedback_cross.hidden = false
+                  return
+                }
+                id_sidebar_feedback_form.classList.remove('was-validated');
+
+                id_sidebar_feedback_icon.hidden = true
+                id_sidebar_feedback_spinner.hidden = false
+                try {
+                  await api_call(
+                    '/api/feedback/send_message.json',
+                    location.href,
+                    id_sidebar_feedback_message.value.slice(0, 65536)
+                  )
+                }
+                catch (error) {
+                  let problem = Problem.from(error)
+
+                  id_sidebar_feedback_cross.hidden = false
+                  id_sidebar_feedback_spinner.hidden = true
+
+                  id_sidebar_feedback_alert.textContent = problem.detail
+                  //id_sidebar_feedback_alert.classList.remove('alert-success')
+                  //id_sidebar_feedback_alert.classList.add('alert-danger')
+                  id_sidebar_feedback_alert.hidden = false
+                  return
+                }
+                //id_sidebar_feedback_tick.hidden = false
+                //id_sidebar_feedback_spinner.hidden = true
+                //id_sidebar_feedback_alert.alertContent = 'We have received your message. We will be in touch as soon as possible.'
+                //id_sidebar_feedback_alert.classList.add('alert-success')
+                //id_sidebar_feedback_alert.classList.remove('alert-danger')
+                //id_sidebar_feedback_alert.hidden = false
+
+                id_sidebar_feedback_icon.hidden = false
+                id_sidebar_feedback_spinner.hidden = true
+                id_sidebar_feedback_alert.hidden = true
+                id_sidebar_message_modal_message.textContent = 'Thanks! We have received your feedback.'
+                $('#sidebar-feedback-modal').modal('hide')
+                $('#sidebar-message-modal').modal('show')
+              }
+            )
+
+            // sidebar
+            let sidebar_outer_computed_style = window.getComputedStyle(
+              id_sidebar_outer
+            )
+            let sidebar_toggle_computed_style = window.getComputedStyle(
+              id_sidebar_toggle
+            )
+            let sidebar_is_collapsed =
+              () =>
+                sidebar_toggle_computed_style.display !== 'none' &&
+                  id_sidebar_outer.classList.contains(
+                    'sidebar-outer-collapsed'
+                  )
+            let sidebar_collapse_update = () => {
+              if (sidebar_outer_computed_style.position === 'sticky') { // md and up
+                id_sidebar_outer.style.flexBasis =
+                  sidebar_is_collapsed() ?
+                    '0px' :
+                    `${id_sidebar_inner.clientWidth}px`
+                id_sidebar_outer.style.removeProperty('height')
+              }
+              else {
+                id_sidebar_outer.style.height =
+                  sidebar_is_collapsed() ?
+                    '0px' :
+                    `${id_sidebar_inner.clientHeight}px`
+                id_sidebar_outer.style.removeProperty('flex-basis')
+              }
+            }
+            window.addEventListener('resize', sidebar_collapse_update)
+            sidebar_collapse_update()
+
+            id_sidebar_outer.addEventListener(
+              'transitionend',
+              () => {
+                // transitions are only allowed after clicking collapse button,
+                // otherwise they can be triggered by media queries on resize
+                id_sidebar_outer.style.removeProperty('transition-property')
+              }
+            )
+
+            id_sidebar_toggle.addEventListener(
+              'click',
+              () => {
+                if (
+                  id_sidebar_outer.classList.contains(
+                    'sidebar-outer-collapsed'
+                  )
+                )
+                  id_sidebar_outer.classList.remove(
+                    'sidebar-outer-collapsed'
+                  )
+                else
+                   id_sidebar_outer.classList.add(
+                    'sidebar-outer-collapsed'
+                  )
+                id_sidebar_outer.style.transitionProperty =
+                  sidebar_outer_computed_style.position === 'sticky' ?
+                    'flex-basis' : // md and up
+                    'height'
+                sidebar_collapse_update()
+              }
+            )
+          }
+        )
+      }
+
+      await scripts(_out)
+    }
+  )
+}
index e722cfc..4fb9db4 100644 (file)
@@ -7,7 +7,7 @@ return async env => {
     async _out => {},
     // body
     async _out => {
-      p.mt-3 {
+      p {
         'This week with some trepidation we have started to homeschool A5 and C4. Until recently, they had been attending daycare with integrated kindergarten 3 days a week together. This had been satisfactory, and had allowed me to attend full-time employment whilst I was full time, and my wife to graduate from her degree.'
       }
 
index 17a4e11..7f8465c 100644 (file)
@@ -7,7 +7,7 @@ return async env => {
     async _out => {},
     // body
     async _out => {
-      p.mt-3 {
+      p {
         'And now for the rather belated update in which I detail the first week of homeschooling, what we studied, what the kids had trouble with, what I learned from the process, and some samples of the teaching materials and kids’ work.'
       }
 
index 428d3d8..3515c9f 100644 (file)
@@ -1,9 +1,9 @@
 return async env => {
   //let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
   let get_navigation = await _require('/_lib/get_navigation.jst')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
-  await navbar(
+  await sidebar(
     env,
     async _out => {},
     async _out => {
index a9f149c..648dc11 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload draft details if any
   let transaction = await env.site.database.Transaction()
@@ -28,7 +28,7 @@ return async env => {
     throw error
   }
 
-  await navbar(
+  await sidebar(
     env,
     // head
     async _out => {},
index 9769214..14ab5de 100644 (file)
@@ -18,9 +18,8 @@ $footer-link-hover-color: darken($footer-link-color, 10%);
   }
 }
 
-// place a container div around entire page, and then use this
-// to make the background on navbar or footer appear full-width
-.extend-background {
+// use within a container to extend the background to left/right
+.container-background {
   margin-left: calc(-.5 * (100vw - 100%));
   margin-right: calc(-.5 * (100vw - 100%));
   padding-left: calc(.5 * (100vw - 100%));
index e1b481c..c6111c0 100644 (file)
@@ -198,8 +198,9 @@ $grid-breakpoints: (
   md: 768px,
   lg: 992px,
   xl: 1200px,
-  2xl: 1500px, // Nick
-  3xl: 1800px // Nick
+  // Nick (for sidebar)
+  2xl: 1500px,
+  3xl: 1800px
 ) !default;
 
 @include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
@@ -215,8 +216,9 @@ $container-max-widths: (
   md: 720px,
   lg: 960px,
   xl: 1140px,
-  2xl: 1420px, // Nick
-  3xl: 1700px // Nick
+  // Nick (for sidebar)
+  2xl: 1420px,
+  3xl: 1700px
 ) !default;
 
 @include _assert-ascending($container-max-widths, "$container-max-widths");
index a4c945d..afe9bac 100644 (file)
@@ -1,8 +1,8 @@
 return async env => {
   //let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
-  await navbar(
+  await sidebar(
     env,
     // head
     async _out => {},
index 6c3d538..2e7a943 100644 (file)
@@ -11,7 +11,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload draft details if any
   let transaction = await env.site.database.Transaction()
@@ -56,7 +56,7 @@ return async env => {
   )
     change_details_draft = null
 
-  await navbar(
+  await sidebar(
     env,
     // head
     async _out => {},
index 77add49..b6fe1fd 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload placeholder
   let transaction = await env.site.database.Transaction()
@@ -30,7 +30,7 @@ return async env => {
       decodeURIComponent(env.parsed_url.query.email) :
       ''
 
-   await navbar(
+   await sidebar(
     env,
     // head
     async _out => {},
index ffa9e15..278244b 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload placeholder
   let transaction = await env.site.database.Transaction()
@@ -30,7 +30,7 @@ return async env => {
       decodeURIComponent(env.parsed_url.query.email) :
       ''
 
-   await navbar(
+   await sidebar(
     env,
     // head
     async _out => {},
index 99f7413..847fd4e 100644 (file)
@@ -8,7 +8,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload draft details if any
   let transaction = await env.site.database.Transaction()
@@ -31,7 +31,7 @@ return async env => {
     throw error
   }
 
-  await navbar(
+  await sidebar(
     env,
     // head
     async _out => {},
index 4871e5e..0f1423b 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload placeholder
   let transaction = await env.site.database.Transaction()
@@ -37,7 +37,7 @@ return async env => {
       decodeURIComponent(env.parsed_url.query.link_code) :
       ''
 
-   await navbar(
+   await sidebar(
     env,
     // head
     async _out => {},
index b26fbeb..a158564 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let get_session = await _require('/_lib/get_session.jst')
   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
 
   // preload placeholder
   let transaction = await env.site.database.Transaction()
@@ -37,7 +37,7 @@ return async env => {
       decodeURIComponent(env.parsed_url.query.link_code) :
       ''
 
-   await navbar(
+   await sidebar(
     env,
     // head
     async _out => {},
index 4c481e4..c3da1fd 100644 (file)
@@ -3,7 +3,7 @@ let querystring = require('querystring')
 
 return async env => {
   //let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
-  let navbar = await _require('/_lib/navbar.jst')
+  let sidebar = await _require('/_lib/sidebar.jst')
   let zet_site = await env.site.get_zettair('/_zet/site')
 
   let query = env.parsed_url.query.query
@@ -13,7 +13,7 @@ return async env => {
     `${env.parsed_url.host} search "${query}" first ${first} results ${search.results.length} total results ${search.total_results}`
   )
 
-  await navbar(
+  await sidebar(
     env,
     async _out => {},
     async _out => {