@ 261102.1

230731.1 · c8e28da12973f8844ac0457e2db6ccd56286b34a

Redesign website (#2628)

* Bump up Bootstrap to v5.3.1, Bootstrap Icon to v1.10.5.
* Use autoComplete.js to drop dependency on jQuery and typeahead.js.
* Support dark mode.
* New svg logo and icon with responsive color mode support.
* Normalize section ids to lower kebab-case for easiness of linking.
* Use relative paths for links for local development (--root /output).
* Various markup cleanups and accessibility improvements.

reading declarations

.github/workflows/website.yml

@@ -20,7 +20,7 @@ jobs:       - name: Setup Python         uses: actions/setup-python@v4         with:-          python-version: '3.10'+          python-version: '3.11'           cache: pipenv       - name: Install pipenv         run: pip install pipenv

Makefile.am

@@ -197,14 +197,10 @@ endif  ### Packaging -docs/site.yml: configure.ac-	sed 's/^jq_version: .*/jq_version: "$(VERSION)"/' $@ > $@.new-	mv $@.new $@- install-binaries: $(BUILT_SOURCES) 	$(MAKE) $(AM_MAKEFLAGS) install-exec -DOC_FILES = docs/content docs/public docs/templates docs/site.yml       \+DOC_FILES = docs/content docs/public docs/templates                     \         docs/Pipfile docs/Pipfile.lock docs/build_manpage.py            \         docs/build_mantests.py docs/build_website.py docs/README.md     \         docs/validate_manual_schema.py docs/manual_schema.yml

docs/README.md

@@ -11,16 +11,16 @@ need `python3` and `pipenv`. You can install `pipenv` like so:  Though, you may need to say `pip3` instead, depending on your system. Once you have `pipenv` installed, you can install the dependencies by running-`pipenv sync` from the `docs` directory.+`pipenv sync` from the `docs/` directory.  Also, you may need to run `virtualenv -p /usr/bin/python3 venv/` and then `source venv/bin/activate`, and only then `pipenv sync`.  Once this is done, rerun `./configure` in the jq root directory and then-the Makefile will be able to generate the jq manpage.  You can also just-run `pipenv run build_manpage.py` in the `docs` directory to build the-`jq.1` page manually, and `pipenv run build_mantests.py` to build the-contents of `tests/man.test` and `tests/manonig.test`.+the `Makefile` will be able to generate the jq manpage.  You can just run+`make jq.1` to build the manpage manually, and `make tests/man.test` to+update the manual tests. -To build the website, run `pipenv run ./build_website.py` from inside-the `docs` directory.+To build the website, run `pipenv run python3 build_website.py --root /output`+in the `docs/` directory. To serve them locally, you can run+`python3 -m http.server`.

docs/build_website.py

@@ -1,4 +1,5 @@ #!/usr/bin/env python3+import argparse import glob import itertools from jinja2 import Environment, FileSystemLoader, select_autoescape, pass_context@@ -10,6 +11,10 @@ import re import shutil import yaml +parser = argparse.ArgumentParser()+parser.add_argument('--root', default='/jq')+args = parser.parse_args()+ env = Environment(     loader=FileSystemLoader('templates'),     autoescape=select_autoescape(['html.j2']),@@ -21,40 +26,51 @@ def load_yml_file(fn):         return yaml.safe_load(f)  +env.globals['url'] = 'https://jqlang.github.io/jq'+env.globals['root'] = args.root+ env.filters['search_id'] = lambda input: input.replace(r'`', '')-env.filters['section_id'] = lambda input: re.sub(r"[^a-zA-Z0-9_]", '', input)-env.filters['entry_id'] = lambda input: re.sub(r"[ `]", '', input)+env.filters['section_id'] = lambda input: re.sub(+    r'[^-a-zA-Z0-9_]', '', input.replace(' ', '-')).lower()+env.filters['entry_id'] = lambda input: re.sub(+    r'^(split|first-last-nth)$',+    r'\1' + ('-1' if ';' not in input else '-2'),  # avoid id conflict+    re.sub(+        r'\b([^-]+)(?:-\1)+\b',+        r'\1',  # e.g. range-range-range -> range+        re.sub(r' ?/ ?|,? ', '-',+               re.sub(r'[`;]|: .*|\(.*?\)| \[.+\]', '', input)))).lower() env.filters['markdownify'] = lambda input: Markup(markdown(input))-env.filters['no_paragraph'] = lambda input: Markup(re.sub(r"</?p>", '', input))+env.filters['no_paragraph'] = lambda input: Markup(re.sub(r'</?p>', '', input))  env.globals['unique_id'] = pass_context(     lambda ctx: str(next(ctx['unique_ctr']))) -env.globals.update(load_yml_file('site.yml')) -env.globals['navigation'] = ['tutorial', 'download', 'manual']+def raise_handler(message):+    raise Exception(message)+++env.globals['raise'] = raise_handler  -def generate_file(env, fname='content/1.tutorial/default.yml'):+def generate_file(env, fname):     path, base = os.path.split(fname)     path = os.path.relpath(path, 'content')     if path == '.':         path = ''-        slug = 'index'         permalink = ''     else:-        slug = os.path.basename(path)         permalink = path + '/'      output_dir = os.path.join('output', path)     output_path = os.path.join(output_dir, 'index.html') -    template_name = re.sub(r".yml$", '.html.j2', base)+    template_name = re.sub(r'.yml$', '.html.j2', base)      ctx = load_yml_file(fname)     ctx.update(unique_ctr=itertools.count(1),                permalink=permalink,-               slug=slug,                navitem=path)     os.makedirs(output_dir, exist_ok=True)     env.get_template(template_name).stream(ctx).dump(output_path,@@ -72,6 +88,7 @@ def copy_public_files(root=''):             shutil.copyfile(f.path, dst)  +os.makedirs('output', exist_ok=True) copy_public_files()  for fn in glob.glob('content/**/*.yml', recursive=True):

docs/content/download/default.yml

@@ -196,9 +196,9 @@ body:       #### Building the documentation        jq's documentation is compiled into static HTML using Python.-      To build the docs, run `pipenv run python3 build_website.py` from-      the docs/ subdirectory. To serve them locally, you can run-      `python3 -m SimpleHTTPServer`. You'll need a few Python dependencies,+      To build the docs, run `pipenv run python3 build_website.py --root /output`+      in the `docs/` directory. To serve them locally, you can run+      `python3 -m http.server`. You'll need a few Python dependencies,       which can be installed by following the instructions in `docs/README.md`.        The man page is built by `make jq.1`, or just `make`, also from

docs/content/index.yml

@@ -24,7 +24,7 @@ body3: |  tail: | -  Go read the [tutorial](/jq/tutorial/) for more, or the [manual](/jq/manual/)+  Go read the [tutorial](./tutorial/) for more, or the [manual](./manual/)   for *way* more.    Have a question related to jq? You can seek answers on [Stack Overflow](https://stackoverflow.com/)@@ -34,7 +34,7 @@ tail: | news:   - date: 1 November 2018     body: |-      jq 1.6 released. See installation options on the [download](/jq/download/)+      jq 1.6 released. See installation options on the [download](./download/)       page, and the [release notes](https://github.com/jqlang/jq/releases/tag/jq-1.6)       for details. @@ -44,7 +44,7 @@ news:       jq 1.5 released, including new datetime, math, and regexp functions,       try/catch syntax, array and object destructuring, a streaming parser,       and a module system. See installation options on the-      [download](/jq/download/) page, and the+      [download](./download/) page, and the       [release notes](https://github.com/jqlang/jq/releases/tag/jq-1.5)       for details. @@ -63,7 +63,7 @@ news:   - date: 09 June 2014     body: | -      jq 1.4 (finally) released! Get it on the [download](/jq/download/) page.+      jq 1.4 (finally) released! Get it on the [download](./download/) page.    - date: 19 May 2013     body: |

docs/content/manual/manual.yml

@@ -3,9 +3,8 @@ headline: jq Manual (development version)  history: | -  *For released versions, see [jq 1.6](/jq/manual/v1.6),-  [jq 1.5](/jq/manual/v1.5),  [jq 1.4](/jq/manual/v1.4)-  or [jq 1.3](/jq/manual/v1.3).*+  *For released versions, see [jq 1.6](./v1.6/), [jq 1.5](./v1.5/),+  [jq 1.4](./v1.4/) or [jq 1.3](./v1.3/).*  body: | 

docs/content/manual/v1.3/manual.yml

@@ -4,7 +4,7 @@ headline: jq 1.3 Manual history: |    *The manual for the development version of jq can be found-  [here](/jq/manual).*+  [here](../).*  body: | 

docs/content/manual/v1.4/manual.yml

@@ -4,7 +4,7 @@ headline: jq 1.4 Manual history: |    *The manual for the development version of jq can be found-  [here](/jq/manual).*+  [here](../).*  body: | 

docs/content/manual/v1.5/manual.yml

@@ -4,7 +4,7 @@ headline: jq 1.5 Manual history: |    *The manual for the development version of jq can be found-  [here](/jq/manual).*+  [here](../).*  body: | 

docs/content/manual/v1.6/manual.yml

@@ -4,7 +4,7 @@ headline: jq 1.6 Manual history: |    *The manual for the development version of jq can be found-  [here](/jq/manual).*+  [here](../).*  body: | 

docs/public/css/base.css

deleted file mode 100644@@ -1,173 +0,0 @@-body {-    padding-top: 80px;-}--.container {-    max-width: 970px;-}--/* index.liquid *******************************************/-#blurb {-    padding-top: 40px;-}-#blurb p {-    font-size: 1.9em;-}-#blurb .btn-group {-    margin: 4px;-}--#multiblurb {-    line-height: 1.7;-    text-align: center;-    font-size: 12pt;-}-#multiblurb code {-    border: 0;-    font-size: 12pt;-}--#news {-    font-size: 12pt;-}-#news .date {-    font-style: italic;-}--/* default.liquid *****************************************/-.tutorial-example {-    position: relative;-    margin-bottom: 10px;-}-.tutorial-example pre {-    margin-bottom: 0px;-}-.tutorial-example a {-    position: absolute;-    top: 0px;-    right: 0px;-    padding: 15px 8px;-    color: #777777;-    font-weight: bold;-    line-height: 10px;-    font-size: 12px;-    border-left: 1px solid #DDDDDD;-    display: block;-}-.tutorial-example .accordion-body pre {-    margin: 0 4px;-    border-top: 0;-    border-top-left-radius: 0;-    border-top-right-radius: 0;-}--@media print {-    .tutorial-example a {-        display: none;-    }-}-/* manual.liquid ******************************************/-section {-    padding-top: 24px;-}--h3 code {-    border: 0;-    font-size: 20px;-}--@media (max-width: 991px) {-    #navcolumn {-        /* Put nav column above manual content */-        position: relative !important;-        margin-bottom: 60px;-    }-}-@media (min-width: 992px) {-    #manualcontent {-        /* Put nav column left of manual content */-        padding-left: 280px;-    }-}-.nav-pills {-    margin-bottom: 20px;-}-    .nav-pills li a {-        padding: 8px 12px;-}--.manual-example table {-    border-top: 1px solid #E5E5E5;-}-.manual-example table td {-    white-space: pre-wrap;-    font-family: Monaco, Menlo, Consolas, "Courier New", monospace;-}-.manual-example table td.jqprogram {-    font-weight: bold;-}-.manual-example table th {-    text-align: right;-    padding-right: 10px;-}--@media print {-    #navcolumn {-        display: none !important;-    }--    .manual-example {-        display: block !important;-        height: auto !important;-    }--    .jqplay-btn {-        display: none !important;-    }-}-/* shared/_footer.liquid **********************************/-footer {-    background-color: #F5F5F5;-    padding: 20px 0;-    margin-top: 40px;-    color: #999999;-    text-align: center;-}-footer p {-    margin: 8px 0;-}--/* typeahead **********************************************/-.twitter-typeahead {-    width: 100%;-}--.tt-menu {-    width: 100%;-    background-color: #fff;-    padding: 8px 0;-    border: 1px solid #ccc;-    border: 1px solid rgba(0, 0, 0, 0.2);-    -webkit-border-radius: 8px;-    -moz-border-radius: 8px;-    border-radius: 8px;-    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);-    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);-    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);-}--.tt-suggestion {-    padding: 3px 20px;-}-.tt-suggestion:hover {-    cursor: pointer;-    color: #fff;-    background-color: #446e9b;-}-.tt-suggestion.tt-cursor {-    color: #fff;-    background-color: #446e9b;-}-.tt-suggestion p {-    margin: 0;-}

docs/public/css/base.scss

deleted file mode 100644@@ -1,181 +0,0 @@-@charset "utf-8";-body {-    padding-top: 80px;-}--.container {-    max-width: 970px;-}--/* index.liquid *******************************************/--#blurb {-    padding-top: 40px;-    p {-        font-size: 1.9em;-    }-    .btn-group {-        margin: 4px;-    }-}--#multiblurb {-    line-height: 1.7;-    text-align: center;-    font-size: 12pt;-    code {-        border: 0;-        font-size: 12pt;-    }-}--#news {-    font-size: 12pt;-    .date {-          font-style: italic;-    }-}--/* default.liquid *****************************************/--.tutorial-example {-    position: relative;-    margin-bottom: 10px;-    pre {-        margin-bottom: 0px;-    }-    a {-        position: absolute;-        top: 0px;-        right: 0px;-        padding: 15px 8px;-        color: #777777;-        font-weight: bold;-        line-height: 10px;-        font-size: 12px;-        border-left: 1px solid #DDDDDD;-        display: block;-    }-    .accordion-body pre {-        margin: 0 4px;-        border-top: 0;-        border-top-left-radius: 0;-        border-top-right-radius: 0;-    }-}--@media print {-    .tutorial-example a {-        display: none;-    }-}--/* manual.liquid ******************************************/--section {-    padding-top: 24px;-}--h3 code {-    border: 0;-    font-size: 20px;-}--@media(max-width: 991px){-    #navcolumn {-        /* Put nav column above manual content */-        position: relative !important;-        margin-bottom: 60px;-    }-}--@media(min-width: 992px) {-    #manualcontent {-        /* Put nav column left of manual content */-        padding-left: 280px;-    }-}--.nav-pills {-    li a {-        padding: 8px 12px;-    }-    margin-bottom: 20px;-}--.manual-example table {-    border-top: 1px solid #E5E5E5;-    td {-        white-space: pre-wrap;-        font-family: Monaco, Menlo, Consolas, "Courier New", monospace;-    }-    td.jqprogram {-        font-weight: bold;-    }-    th {-        text-align: right;-        padding-right: 10px;-    }-}--@media print {-    #navcolumn {-        display: none !important;-    }-    .manual-example {-        display: block !important;-        height: auto !important;-    }-    .jqplay-btn {-        display: none !important;-    }-}--/* shared/_footer.liquid **********************************/--footer {-    background-color: #F5F5F5;-    padding: 20px 0;-    margin-top: 40px;-    color: #999999;-    text-align: center;-    p {-        margin: 8px 0;-    }-}--/* typeahead **********************************************/--.twitter-typeahead {-    width: 100%;-}--.tt-menu {-    width: 100%;-    background-color: #fff;-    padding: 8px 0;-    border: 1px solid #ccc;-    border: 1px solid rgba(0, 0, 0, 0.2);-    -webkit-border-radius: 8px;-       -moz-border-radius: 8px;-            border-radius: 8px;-    -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);-       -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);-            box-shadow: 0 5px 10px rgba(0,0,0,.2);-}--.tt-suggestion {-    padding: 3px 20px;-    &:hover {-        cursor: pointer;-        color: #fff;-        background-color: #446e9b;-    }-    &.tt-cursor {-        color: #fff;-        background-color: #446e9b;-    }-    p {-        margin: 0;-    }-}

docs/public/css/style.css

new file mode 100644@@ -0,0 +1,99 @@+main {+  padding: 1rem;++  & * {+    scroll-margin-top: 4rem;+  }++  @media print {+    width: 100%!important;+    --bs-code-color: --bs-body-color;+  }+}++header {+  z-index: 1050!important; /* higher than #contents */+}++section[id] {+  display: flow-root;++  > :first-child {+    .icon-link {+      opacity: 0;++      &:focus {+        opacity: .8;+      }+    }++    &:hover .icon-link {+      opacity: 1;+    }+  }+}++.offcanvas[aria-modal=true] .nav-link {+  padding: .7rem;+}+.offcanvas-md {+  --bs-offcanvas-width: auto;+}++ul {+  list-style: none;+  padding-left: 1rem;+}++pre {+  margin: 0 .5rem 1rem;+  padding: .5rem 1rem;+  background-color: var(--bs-secondary-bg-subtle);+  border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color);+}++button{+  &[aria-expanded=false] .bi-chevron-down {+    display: none;+  }++  &[aria-expanded=true] .bi-chevron-right {+    display: none;+  }+}++mark {+  padding: 0;+}++.container-searchbox {+  position: relative;++  & input:focus ~ kbd {+    display: none;+  }++  & ul {+    position: absolute;+    width: 100%;+    top: 100%;+    padding: 0;+    background-color: var(--bs-body-bg);+    border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color);+  }++  & li {+    padding: .3em .6em;+    white-space: nowrap;+    overflow-x: hidden;++    &[aria-selected=true] {+      background-color: var(--bs-secondary-bg);+    }++    &:hover {+      cursor: pointer;+      background-color: var(--bs-secondary-bg-subtle);+    }+  }+}

docs/public/icon.png

new file mode 100644Binary file changed

docs/public/icon.svg

new file mode 100644@@ -0,0 +1 @@+<svg width="48" height="48" version="1.0" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><style>@media(prefers-color-scheme:dark){g[fill]{filter:invert(1)}}</style><g transform="matrix(.011 0 0 .011 1.94 12)"><g fill="#444"><circle cx="220" cy="1570" r="190"/><path d="m1330 312c-30 41-698 1492-710 1528s25 66 100 101c77 36 102 34 124 3s701-1477 716-1525c15-47-20-73-84-104s-116-44-146-3z"/></g><g fill="#111"><circle cx="2410" cy="200" r="190"/><path d="m1830 655c0 129 0 130 70 130h360v423c0 246-5 474-10 506-19 112-88 176-190 176-59 0-120-27-147-66-51-73-65-84-99-84-31 0-40 8-104 93-84 111-86 129-32 189 227 252 692 187 836-118 53-112 56-153 56-795v-514c0-64-30-100-100-100h-490c-149 0-150 0-150 160zM2867 1233c30 302 250 499 438 527 144 19 244 0 375-90v410c0 81 0 100 150 100 136 0 150-20 150-150v-1420c0-99-19-100-150-100s-139 15-139 82c-103-89-351-149-523-49s-342 278-301 690zm803-107c0 292-105 356-237 356-178 0-249-147-273-296-27-168 25-420 262-420 171 0 248 89 248 360z"/></g></g></svg> 

docs/public/jq.png

deleted file mode 100644Binary file changed

docs/public/jq.svg

new file mode 100644@@ -0,0 +1 @@+<svg width="400" height="220" version="1.0" viewBox="0 0 400 220" xmlns="http://www.w3.org/2000/svg"><style>@media (prefers-color-scheme:dark){g[fill]{filter:invert(1);stroke:none}}</style><g transform="scale(.1)" stroke="#fff" stroke-width="10"><g fill="#444"><circle cx="220" cy="1570" r="190"/><path d="m1330 312c-30 41-698 1492-710 1528s25 66 100 101c77 36 102 34 124 3s701-1477 716-1525c15-47-20-73-84-104s-116-44-146-3z"/></g><g fill="#111"><circle cx="2410" cy="200" r="190"/><path d="m1830 655c0 129 0 130 70 130h360v423c0 246-5 474-10 506-19 112-88 176-190 176-59 0-120-27-147-66-51-73-65-84-99-84-31 0-40 8-104 93-84 111-86 129-32 189 227 252 692 187 836-118 53-112 56-153 56-795v-514c0-64-30-100-100-100h-490c-149 0-150 0-150 160zM2867 1233c30 302 250 499 438 527 144 19 244 0 375-90v410c0 81 0 100 150 100 136 0 150-20 150-150v-1420c0-99-19-100-150-100s-139 15-139 82c-103-89-351-149-523-49s-342 278-301 690zm803-107c0 292-105 356-237 356-178 0-249-147-273-296-27-168 25-420 262-420 171 0 248 89 248 360z"/></g></g></svg> 

docs/public/js/manual-search.js

@@ -1,52 +1,34 @@-var section_names = function(q) {-  if (!q) {-    return [];-  }-  var matches = [];-  q = q.toLowerCase();-  $.each(section_map, function(k, v) {-    if (k.toLowerCase().indexOf(q) != -1) {-      matches.push(k);-    }-  });-  matches.sort(function(a, b) {-    // shortest to longest-    return a.length - b.length;+(() => {+  const searchInput = document.querySelector('input#searchbox');+  const sectionIDs = JSON.parse(document.querySelector('#section-ids').innerText);+  const sanitize = (string) => string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');+  new autoComplete({+    selector: `#${searchInput.id}`,+    wrapper: false,+    data: {+      src: Object.keys(sectionIDs),+      filter: (list) => list.sort((x, y) =>+        x.match.indexOf('<') - y.match.indexOf('<') || x.value.length - y.value.length),+    },+    searchEngine: (query, value) => {+      const index = value.toLowerCase().indexOf(query.toLowerCase());+      if (index >= 0) {+        return sanitize(value.substring(0, index)) ++          `<mark>${sanitize(value.substring(index, index + query.length))}</mark>` ++          sanitize(value.substring(index + query.length));+      }+    },   });-  return matches;-}-var section_names_cb = function(q, cb) {-  cb(section_names(q));-}-var go_to_section = function() {-  query = $('#searchbox').val();-  results = section_names(query);-  if (results.length == 0) {-    return;-  }-  result = results[0];-  location.hash = '#' + section_map[result];-  if (result != query) {-    $('#searchbox').val(result);-  }-}-$(function(){-  $('#searchbox').typeahead(-    {hint: false, highlight: true, minLength: 1},-    {name: "contents", source: section_names_cb, limit: 6}-  ).on('typeahead:selected', function(e, data) {-    go_to_section();+  searchInput.addEventListener('selection', (event) => {+    event.target.value = event.detail.selection.value;+    location.hash = `#${sectionIDs[event.detail.selection.value]}`;   });-  $('#searchbox').change(go_to_section);-});-// add "Run" button to execute examples on jqplay.org-$(function() {-  $.each($('.manual-example table'), function(index, value) {-    $value = $(value)-    var j = $value.find('tr:nth-child(2) td:first').text();-    var q = $value.find('.jqprogram').text().replace(/^jq /, '').replace(/(\r\n|\n|\r)/gm," ").replace(/^'(.+)'$/, '$1');-    var url = 'https://jqplay.org/jq?q=' + encodeURIComponent(q) +'&j=' + encodeURIComponent(j)-    var $last_tr = $value.find('tr:last');-    $last_tr.after('<tr class="jqplay-btn"><th><a href="' + url + '" class="btn btn-primary btn-sm">Run</a></th><th></th></tr><tr><th></th><th></th></tr>');+  document.addEventListener('keydown', (event) => {+    if (event.code === 'Slash' && !event.altKey && !event.ctrlKey && !event.metaKey+      && !event.shiftKey && !/^(INPUT|TEXTAREA)$/.test(event.target.nodeName)) {+      searchInput.focus();+      searchInput.select();+      event.preventDefault();+    }   });-});+})();

docs/site.yml

deleted file mode 100644@@ -1,9 +0,0 @@-# The key value pairs found below are available within the templates.--url: https://jqlang.github.io/jq--# This line is modified by the Makefile. To change the version number,-# edit the Autoconf version number at the top of configure.ac-jq_version: "1.6-159-gcff5336-dirty"--root: '/jq'

docs/templates/default.html.j2

@@ -2,32 +2,29 @@ <html lang="en"> {% include "shared/_head.html.j2" %} -  <body id="{{slug}}">+  <body> {% include "shared/_navbar.html.j2" %} -    <div class="container">-      <div class="row">-        <h1>{{headline}}</h1>-        {% for item in body %}-          {% if item.text %}-            {{ item.text | replace('$JQ_VERSION', jq_version) | markdownify }}-          {% endif %}+    <main id="main" class="container-lg">+      <h1>{{ headline }}</h1>+      {%- for item in body %}+      {%- if item.text %}+      {{ item.text | markdownify }}+      {%- endif %} -          {% if item.command %}-            {% set resultID = unique_id() %}-            <div class="tutorial-example">-              <div class="accordion-heading">-                <pre>{{item.command}}</pre>-                <a data-toggle="collapse" href="#result{{resultID}}">Show result</a>-              </div>-              <div id="result{{resultID}}" class="accordion-body collapse">-                <pre>{{item.result}}</pre>-              </div>-            </div>-          {% endif %}-        {% endfor %}+      {%- if item.command %}+      {%- set resultID = unique_id() %}+      <div class="tutorial-example mb-3">+        <div class="d-flex accordion-heading me-2">+          <pre class="flex-grow-1 me-0 mb-0" tabindex="0">{{ item.command }}</pre>+          <button type="button" class="btn btn-sm btn-secondary text-body-secondary bg-secondary-subtle link-body-emphasis flex-shrink-0 d-flex align-items-center border border-start-0 d-print-none"+            data-bs-toggle="collapse" data-bs-target="#result{{ resultID }}" aria-expanded="false" aria-controls="result{{ resultID }}">Show result</button>+        </div>+        <pre id="result{{ resultID }}" class="accordion-body collapse p-3 border-top-0 d-print-block" tabindex="0">{{ item.result }}</pre>       </div>-    </div>+      {%- endif %}+      {%- endfor %}+    </main>  {% include "shared/_footer.html.j2" %}   </body>

docs/templates/index.html.j2

@@ -2,71 +2,54 @@ <html lang="en"> {% include "shared/_head.html.j2" %} -  <body id="{{ slug }}">+  <body> {% include "shared/_navbar.html.j2" %} -    <div class="container">+    <main id="main" class="container-lg">       <div class="row">-        <div class="jumbotron">-          <div class="row">-            <div class="col-sm-6">-              <img src="{{root}}/jq.png" class="img-responsive" alt="jq logo"-                  width="400" height="220">-            </div>-            <div class="col-sm-6">-              <div id="blurb">-                {{blurb | markdownify}}--                <div class="btn-toolbar" role="toolbar">-                  <div class="btn-group" role="group">-                    <a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">-                      Download jq 1.6-                      <span class="caret"></span>-                    </a>-                    <ul class="dropdown-menu">-                      <li><a href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64">Linux (64-bit)</a></li>-                      <li><a href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-osx-amd64">macOS (64-bit)</a></li>-                      <li><a href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-win64.exe">Windows (64-bit)</a></li>-                      <li><a href="{{root}}/download/">Other platforms, older versions, and source</a></li>-                    </ul>-                  </div>--                  <div class="btn-group">-                    <a class="btn btn-primary" href="https://jqplay.org">-                      Try online at jqplay.org!-                    </a>-                  </div>-                </div>-              </div>-            </div>+        <div class="col-md-6 text-center p-3">+          <h1><img src="{{ root }}/jq.svg" class="img-fluid" alt="jq" width="400" height="220"></h1>+        </div>+        <div class="col-md-6 d-flex flex-column justify-content-center text-center align-items-center">+          <h2 class="px-1" style="width:16em">{{ blurb }}</h2>+          <div class="btn-group d-print-none" role="group">+            <button type="button" class="btn btn-primary dropdown-toggle text-nowrap" data-bs-toggle="dropdown" aria-expanded="false">+              Download jq 1.6+              <span class="caret"></span>+            </button>+            <ul class="dropdown-menu">+              <li><a class="dropdown-item" href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64" aria-label="Link to download executable: Linux (64-bit)"><span class="bi bi-download me-2" aria-hidden="true"></span>Linux (64-bit)</a></li>+              <li><a class="dropdown-item" href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-osx-amd64" aria-label="Link to download executable: macOS (64-bit)"><span class="bi bi-download me-2" aria-hidden="true"></span>macOS (64-bit)</a></li>+              <li><a class="dropdown-item" href="https://github.com/jqlang/jq/releases/download/jq-1.6/jq-win64.exe" aria-label="Link to download executable: Windows (64-bit)"><span class="bi bi-download me-2" aria-hidden="true"></span>Windows (64-bit)</a></li>+              <li><a class="dropdown-item" href="{{ root }}/download/">Other platforms, older versions, and source</a></li>+            </ul>+            <a class="btn btn-primary text-nowrap" href="https://jqplay.org" target="_blank" rel="noopener">+              Try online at jqplay.org!<span class="bi bi-box-arrow-up-right ms-1" aria-hidden="true"></span>+            </a>           </div>         </div>       </div> -      <div class="row" id="multiblurb">-        <div class="col-sm-4">{{body1 | markdownify}}</div>-        <div class="col-sm-4">{{body2 | markdownify}}</div>-        <div class="col-sm-4">{{body3 | markdownify}}</div>+      <div class="row my-3">+        <div class="col-md-4">{{ body1 | markdownify }}</div>+        <div class="col-md-4">{{ body2 | markdownify }}</div>+        <div class="col-md-4">{{ body3 | markdownify }}</div>       </div> -      <div class="row" style="text-align:center; margin-top: 30px">-        {{tail | markdownify}}+      <div class="text-center my-3">+        {{ tail | markdownify }}       </div> -      <div class="row">-        <h2>News</h2>-        <div id="news">-          <ul>-            {% for item in news %}-              <li>-                <span class="date">{{item.date}}</span>-                {{item.body | markdownify}}-              </li>-            {% endfor %}-          </ul>-        </div>-      </div>-    </div>+      <h2>News</h2>+      <ul>+        {%- for item in news %}+        <li>+          <span class="fst-italic">{{ item.date }}</span>+          {{ item.body | markdownify }}+        </li>+        {%- endfor %}+      </ul>+    </main>  {% include "shared/_footer.html.j2" %}   </body>

docs/templates/manual.html.j2

@@ -2,98 +2,109 @@ <html lang="en"> {% include "shared/_head.html.j2" %} -  <body id="{{slug}}" data-spy="scroll" data-target="#navcolumn" data-offset="100">+  <body> {% include "shared/_navbar.html.j2" %} -    <div class="container">-      <div class="row">-        <div class="affix" id="navcolumn">+    <div class="container-lg row align-items-start mx-auto p-3">++      <button type="button" class="d-md-none w-auto position-fixed bottom-0 end-0 p-2 m-3 bg-body-secondary border-0 text-body d-print-none"+        data-bs-toggle="offcanvas" data-bs-target="#contents" aria-controls="contents" aria-expanded="false" aria-label="Toggle table of contents">+        <span class="bi bi-list" aria-hidden="true"></span>+      </button>+      <nav id="contents" class="col-md-3 sticky-md-top p-3 overflow-y-auto offcanvas-md offcanvas-end d-print-none" style="top:4.5rem; height:calc(100dvh - 5.5rem);" aria-label="Table of contents">+        <div class="d-flex justify-content-between">           <h4>Contents</h4>-          <ul class="nav nav-pills nav-stacked">-            {% for section in sections %}-              <li>-                <a href="#{{section.title | section_id}}">{{section.title}}</a>-              </li>-            {% endfor %}-          </ul>-          <form class="form-group" onsubmit="go_to_section(); return false;">-            <input type="text"-                  class="form-control"-                  placeholder="Search"-                  autocomplete="off"-                  id="searchbox">-          </form>+          <button type="button" class="btn-close d-md-none" data-bs-dismiss="offcanvas" data-bs-target="#contents" aria-label="Close table of contents"></button>         </div>+        <ul class="offcanvas-md-body nav nav-pills flex-column">+          {%- for section in sections %}+          <li class="nav-item" data-bs-dismiss="offcanvas" data-bs-target="#contents">+            <a class="nav-link" href="#{{ section.title | section_id }}">{{ section.title }}</a>+          </li>+          {%- endfor %}+        </ul>+      </nav>+      {%- set section_ids = {} %}+      {%- macro check_section_id(id) -%}+        {%- if section_ids.__contains__(id) %}+          {{- raise('Duplicate section id: ' ~ id) }}+        {%- endif %}+        {%- set _ = section_ids.__setitem__(id, true) %}+      {%- endmacro %}+      <main id="main" class="col-md-9" data-bs-spy="scroll" data-bs-target="#contents" data-bs-threshold="0,1" data-bs-root-margin="-30% 0% -70%">+        <h1>{{ headline }}</h1>+        {{ history | markdownify }}+        {{ body | markdownify }}+        {%- for section in sections %}+        <section id="{{ section.title | section_id }}">{{ check_section_id(section.title | section_id) }}+          <h2>{{ section.title }} <a href="#{{ section.title | section_id }}" class="icon-link" aria-label="Link to this section: {{ section.title }}"><span class="bi bi-link-45deg" aria-hidden="true"></span></a></h2>+          {{ section.body | markdownify if section.body }}+          {%- for entry in section.entries %}+          <section id="{{ entry.title | entry_id }}">{{ check_section_id(entry.title | entry_id) }}+            <h3>+              {{ entry.title | markdownify | no_paragraph }}+              <a href="#{{ entry.title | entry_id }}" class="icon-link" aria-label="Link to this section: {{ entry.title }}"><span class="bi bi-link-45deg" aria-hidden="true"></span></a>+            </h3>+            {{ entry.body | markdownify }}+            {%- if entry.examples %}+            <div class="pb-3">+              {%- set exampleID = unique_id() %}+              <button type="button" class="btn btn-sm btn-secondary text-body-secondary bg-transparent link-body-emphasis border-0" data-bs-toggle="collapse" data-bs-target="#example{{ exampleID }}" aria-expanded="false" aria-controls="example{{ exampleID }}">+                <span class="me-1 d-print-none" aria-hidden="true"><span class="bi bi-chevron-right"></span><span class="bi bi-chevron-down"></span></span>Example{% if entry.examples | length > 1 %}s{% endif %}+              </button>+              <div id="example{{ exampleID }}" class="collapse mx-3 small d-print-block">+                {%- for example in entry.examples %}+                <table class="table table-borderless table-sm w-auto">+                  <tr>+                    <th class="pe-3">Command</th>+                    <td class="font-monospace">jq '{{ example.program }}'</td>+                  </tr>+                  <tr>+                    <th>Input</th>+                    <td class="font-monospace">{{ example.input }}</td>+                  </tr>+                  {%- if not example.output[0] %}+                  <tr>+                    <th>Output</th>+                    <td class="fst-italic">none</td>+                  </tr>+                  {%- endif %}+                  {%- for output in example.output %}+                  <tr>+                    <th>{% if loop.first %}Output{% endif %}</th>+                    <td class="font-monospace">{{ output }}</td>+                  </tr>+                  {%- endfor %}+                  <tr class="d-print-none">+                    <th><a href="https://jqplay.org/jq?q={{ example.program | urlencode }}&j={{ example.input | urlencode }}" class="btn btn-outline-primary btn-sm" target="_blank" rel="noopener">Run<span class="bi bi-box-arrow-up-right ms-2" aria-hidden="true"></span></a></th>+                    <td></td>+                  </tr>+                </table>+                {%- endfor %}+              </div>+            </div>+            {%- endif %}+          </section>+          {%- endfor %}+        </section>+        {%- endfor %}+      </main> -        <div id="manualcontent">-          <h1>{{headline}}</h1>-          {{ history | markdownify }}-          {{ body | markdownify }}-          {% for section in sections %}-            <section id="{{section.title | section_id}}">-              <h2>{{section.title}}</h2>-              {{ (section.body | markdownify) if section.body }}-              {% for entry in section.entries %}-                <section id="{{entry.title | entry_id}}">-                  <h3>-                    {{entry.title | markdownify | no_paragraph }}-                    {% if entry.subtitle %}<small>{{entry.subtitle}}</small>{% endif %}-                  </h3>-                  {{entry.body | markdownify}}--                  {% if entry.examples %}-                    <div>-                      {% set exampleID = unique_id() %}-                      <a data-toggle="collapse" href="#example{{exampleID}}">-                        <i class="glyphicon glyphicon-chevron-right"></i>-                        {% if entry.examples | length > 1 %}Examples{%else%}Example{%endif%}-                      </a>-                      <div id="example{{exampleID}}" class="manual-example collapse">-                        {% for example in entry.examples %}-                          <table>-                            <tr><th></th><td class="jqprogram">jq '{{ example.program }}'</td></tr>-                            <tr><th>Input</th><td>{{ example.input }}</td></tr>-                            {% if not example.output[0] %}-                              <tr>-                                <th>Output</th>-                                <td><i>none</i></td>-                              </tr>-                            {% endif %}-                            {% for output in example.output %}-                              <tr>-                                {% if loop.first %}-                                  <th>Output</th>-                                {% else %}-                                  <th></th>-                                {% endif %}-                                <td>{{ output }}</td>-                              </tr>-                            {% endfor %}-                          </table>-                        {% endfor %}-                      </div>-                    </div>-                  {% endif %}-                </section>-              {% endfor %}-            </section>-          {% endfor %}-        </div>-      </div>     </div>  {% include "shared/_footer.html.j2" %}-    <script>-      var section_map = {-        {% for section in sections %}-          {% for entry in section.entries %}-            {{entry.title | search_id | tojson }} : {{entry.title | entry_id | tojson }},-          {% endfor %}-          {{section.title | search_id | tojson }} : {{section.title | section_id | tojson }}-          {% if not loop.last %},{% endif %}-        {% endfor %}-      };+    <script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"+      integrity="sha384-xbzjoN6H5XHmAqoSSR42hZVRninPGx85y+XZQGlWxVu2y91lTmr/oD80i5cjeUBv" crossorigin="anonymous"></script>+    <script id="section-ids" type="application/json">+      {+        {%- for section in sections %}+        {%- for entry in section.entries %}+        {{ entry.title | search_id | tojson }}: {{ entry.title | entry_id | tojson }},+        {%- endfor %}+        {{ section.title | search_id | tojson }}: {{ section.title | section_id | tojson }}{{ "," if not loop.last }}+        {%- endfor %}+      }     </script>-    <script src="{{root}}/js/manual-search.js"></script>+    <script src="{{ root }}/js/manual-search.js"></script>   </body> </html>

docs/templates/shared/_footer.html.j2

@@ -1,10 +1,14 @@-    <footer>-      <div class="container">-        <p>This website is made with <a href="http://getbootstrap.com">Bootstrap</a>, themed with <a href="https://bootswatch.com">Bootswatch</a>.</p>-        <p>jq is licensed under the MIT license (code) and the <a href="https://creativecommons.org/licenses/by/3.0/">CC-BY-3.0</a> license (docs).</p>+    <footer class="bd-footer bg-body-tertiary">+      <div class="py-3 text-center">+        <p>+          This website is made with <a href="https://getbootstrap.com" target="_blank" rel="noopener">Bootstrap</a>,+          themed with <a href="https://bootswatch.com" target="_blank" rel="noopener">Bootswatch</a>.+        </p>+        <p>+          jq is licensed under the MIT license (code) and the+          <a href="https://creativecommons.org/licenses/by/3.0/" target="_blank" rel="noopener">CC-BY-3.0</a> license (docs).+        </p>       </div>     </footer>--    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>-    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha256-Sk3nkD6mLTMOF0EOpNtsIry+s1CsaqQC1rVLTAy+0yc= sha512-K1qjQ+NcF2TYO/eI3M6v8EiNYZfA95pQumfvcVrTHtwQVDG+aHRqLi/ETn2uB+1JqwYqVG3LIvdm9lj6imS/pQ==" crossorigin="anonymous"></script>-    <script src="https://twitter.github.io/typeahead.js/releases/0.11.1/typeahead.bundle.js"></script>+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"+      integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>

docs/templates/shared/_head.html.j2

@@ -1,17 +1,23 @@   <head>-    <script>-      if ((window.location.host == "jqlang.github.io") && (window.location.protocol != "https:"))-        window.location.protocol = "https";-    </script>     <meta charset="utf-8">-    <title>{{headline}}</title>+    <title>{{ headline }}</title>     <meta name="viewport" content="width=device-width, initial-scale=1">-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />-    <link rel="canonical" href="{{ url }}/{{ permalink }}" />-    <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/spacelab/bootstrap.min.css" rel="stylesheet" integrity="sha256-j7Dtnd7ZjexEiPNbscbopFn9+Cs0b3TLipKsWAPHZIM= sha512-RFhfi6P8zWMAJrEGU+CPjuxPh3r/UUBGqQ+/o6WKPIVZmQqeOipGotH2ihRULuQ8wsMBoK15TSZqc/7VYWyuIw==" crossorigin="anonymous">-    <link rel="stylesheet" href="{{ root }}/css/base.css" type="text/css">-    <!--[if lt IE 9]>-      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>-      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>-    <![endif]-->+    <link rel="icon" href="{{ root }}/icon.svg" type="image/svg+xml">+    <link rel="apple-touch-icon" href="{{ root }}/icon.png" type="image/png">+    <link rel="canonical" href="{{ url }}/{{ permalink }}">+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.1/dist/cosmo/bootstrap.min.css"+      integrity="sha384-dulfW0vmzZ638jigSgZXvDxMmd70GCnIv6oa+riKq6Kk4E0MKf7qmBfwP02wltv5" crossorigin="anonymous">+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"+      integrity="sha384-Ay26V7L8bsJTsX9Sxclnvsn+hkdiwRnrjZJXqKmkIDobPgIIWBOVguEcQQLDuhfN" crossorigin="anonymous">+    <link rel="stylesheet" href="{{ root }}/css/style.css" type="text/css">+    <script>+      (function() {+        function setTheme(mediaQuery) {+          document.documentElement.setAttribute('data-bs-theme',+            document.documentElement.style.colorScheme = mediaQuery.matches ? 'dark' : 'light');+        }+        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');+        setTheme(mediaQuery); mediaQuery.addEventListener('change', setTheme);+      })();+    </script>   </head>

docs/templates/shared/_navbar.html.j2

@@ -1,27 +1,40 @@-    <div class="navbar navbar-default navbar-fixed-top" role="navigation">-      <div class="container">-        <div class="navbar-header">-          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#nav-collapse">-            <span class="sr-only">Toggle navigation</span>-            <span class="icon-bar"></span>-            <span class="icon-bar"></span>-            <span class="icon-bar"></span>-          </button>-          <a class="navbar-brand" href="{{root}}/">jq</a>-        </div>--        <div class="navbar-collapse collapse" id="nav-collapse">-          <ul class="nav navbar-nav">-            {% for item in navigation %}-              <li {% if item == navitem %} class="active" {% endif %}>-                <a href="{{root}}/{{item}}/">{{item | capitalize}}</a>-              </li>-            {% endfor %}-            <li><a href="https://github.com/jqlang/jq/issues">Issues</a></li>-            <li><a href="https://github.com/jqlang/jq">Source</a></li>-            <li><a href="https://jqplay.org">Try online!</a></li>-            <li><a href="https://raw.githubusercontent.com/jqlang/jq/master/NEWS.md">News</a></li>+    <div class="container visually-hidden-focusable">+      <a class="d-inline-flex p-2" href="#main">Skip to main content</a>+      {%- if navitem.startswith('manual') %}+      <a class="d-inline-flex p-2 m-1" href="#contents">Skip to table of contents</a>+      {%- endif %}+    </div>+    <header class="navbar navbar-expand-md sticky-top bg-body-tertiary d-print-none">+      <nav class="container-lg" aria-label="Page navigation">+        <button type="button" class="navbar-toggler" data-bs-toggle="offcanvas" data-bs-target="#navbar-collapse"+          aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle page navigation">+          <span class="navbar-toggler-icon"></span>+        </button>+        <a class="navbar-brand" href="{{ root }}/" aria-label="Top page"><img src="{{ root }}/jq.svg" alt="jq logo" style="height:1.5rem"></a>+        <div id="navbar-collapse" class="offcanvas offcanvas-start navbar-collapse w-auto" aria-labelledby="navbar-title">+          <div class="offcanvas-header">+            <h3 id="navbar-title" class="me-3">jq</h3>+            <button type="button" class="d-md-none btn-close" data-bs-dismiss="offcanvas" aria-label="Close page navigation"></button>+          </div>+          <ul class="offcanvas-body navbar-nav me-auto text-nowrap">+            <li class="nav-item d-md-none"><a class="nav-link{% if not navitem %} active{% endif %}"{% if not navitem %} aria-current="page"{% endif %} href="{{ root }}/">Top page</a></li>+            {%- for item in ['tutorial', 'download', 'manual'] %}+            <li class="nav-item"><a class="nav-link{% if item == navitem %} active{% endif %}"{% if item == navitem %} aria-current="page"{% endif %} href="{{ root }}/{{ item }}/">{{ item | capitalize }}</a></li>+            {%- endfor %}+            <li class="nav-item"><a class="nav-link" href="https://github.com/jqlang/jq" target="_blank" rel="noopener">GitHub</a></li>+            <li class="nav-item"><a class="nav-link" href="https://github.com/jqlang/jq/issues" target="_blank" rel="noopener">Issues</a></li>+            <li class="nav-item"><a class="nav-link" href="https://jqplay.org" target="_blank" rel="noopener">Try online!</a></li>+            <li class="nav-item"><a class="nav-link" href="https://raw.githubusercontent.com/jqlang/jq/master/NEWS.md" target="_blank" rel="noopener">News</a></li>           </ul>         </div>-      </div>-    </div>+        {%- if navitem.startswith('manual') %}+        <div class="container-searchbox form-control p-0 d-none d-md-flex d-print-none border" style="width:16rem">+          <span class="bi bi-search my-auto ms-2 me-1" aria-hidden="true"></span>+          <input type="text" id="searchbox" class="form-control border-0 px-1" placeholder="Search manual" role="combobox"+            aria-label="Search manual" aria-keyshortcuts="/" aria-expanded="false" autocomplete="off" spellcheck="false">+          <kbd class="mx-1 my-auto text-body bg-body rounded border" style="padding:.1rem .2rem" aria-hidden="true">/</kbd>+        </div>+        {%- endif %}+        <a class="d-md-none bi bi-github fs-4 text-body" href="https://github.com/jqlang/jq" target="_blank" rel="noopener" aria-label="GitHub"></a>+      </nav>+    </header>