diff --git a/_includes/nav.html b/_includes/nav.html index 5ec9872526368f29084d7493ac082b8b3016661a..19a69e46234422be438e0805bc131d684daaa062 100644 --- a/_includes/nav.html +++ b/_includes/nav.html @@ -1,73 +1,140 @@ {%- comment -%} - Pages with no `title` are implicitly excluded from the navigation. + The `nav_order` values of pages affect the order in which they are shown in + the navigation panel and in the automatically generated tables of contents. + Sibling pages with the same `nav_order` value may be shown in any order. + Sibling pages with no `nav_order` value are shown after all pages that have + explicit `nav_order` values, ordered by their `title` values. - The values of `title` and `nav_order` can be numbers or strings. - Jekyll gives build failures when sorting on mixtures of different types, - so numbers and strings need to be sorted separately. + The `nav_order` and `title` values can be numbers or strings. To avoid build + failures, we sort numbers and strings separately. We sort numbers by their + values, and strings lexicographically. The case-sensitivity of string sorting + is determined by the configuration setting of `nav_sort`. Pages with no `title` + value are excluded from the navigation. + + Note: Numbers used as `title` or `nav_order` values should not be in quotes, + unless you intend them to be lexicographically ordered. Numbers are written + without spaces or thousands-separators. Negative numbers are preceded by `-`. + Floats are written with the integral and fractional parts separated by `.`. + (Bounds on the magnitude and precision are presumably the same as in Liquid.) +{%- endcomment -%} - Here, numbers are sorted by their values, and come before all strings. - An omitted `nav_order` value is equivalent to the page's `title` value - (except that a numerical `title` value is treated as a string). +{%- assign title_pages = include.pages + | where_exp: "item", "item.title != nil" -%} - The case-sensitivity of string sorting is determined by `site.nav_sort`. +{%- comment -%} + A page with `nav_exclude: true` does not appear in the main navigation. + If it has a `parent`, it may appear in the parent's table of contents. + If it specifies `has_children: true`, it should appear in the breadcrumbs + of the child pages, but its order in relation to other pages is irrelevant. + Pages that never appear can be removed from the pages that need to be sorted. + This optimisation can be significant on a site with many pages. + + In Jekyll 4, the pages to be sorted can be filtered by: + + {%- assign title_pages = title_pages + | where_exp: "item", "item.nav_exclude != true or item.parent != nil" -%} + + That filter is not allowed in Jekyll 3. The following iterative code gives the + same effect, but it is activated only when it will filter more than 50% of the + pages. {%- endcomment -%} -{%- assign titled_pages = include.pages - | where_exp: "item", "item.title != nil" -%} +{%- assign unsorted_pages = title_pages + | where_exp: "item", "item.parent == nil" + | where_exp: "item", "item.nav_exclude == true"-%} +{%- assign title_pages_size = title_pages.size -%} +{%- assign unsorted_pages_percent = unsorted_pages.size + | times: 100 | divided_by: title_pages_size -%} +{%- if unsorted_pages_percent > 50 -%} + {%- assign sorted_pages = "" | split: "" -%} + {%- for item in title_pages -%} + {%- if item.nav_exclude != true or item.parent -%} + {%- assign sorted_pages = sorted_pages | push: item -%} + {%- endif -%} + {%- endfor -%} + {%- assign title_pages = sorted_pages -%} +{%- endif -%} -{%- assign string_ordered_pages = titled_pages +{%- assign nav_order_pages = title_pages + | where_exp: "item", "item.nav_order != nil" -%} +{%- assign title_order_pages = title_pages | where_exp: "item", "item.nav_order == nil" -%} -{%- assign nav_ordered_pages = titled_pages - | where_exp: "item", "item.nav_order != nil" -%} {%- comment -%} - Add the nav-ordered pages to the number-ordered pages or the string-ordered pages, - depending on their `nav_order` value. + Divide the arrays of `nav_order_pages` and `title_order_pages` according to + the type of value. - The first character of the `jsonify` result is `"` only for strings. + The first character of the result of `jsonify` is `"` only for strings. + Grouping by a single character also ensures the number of groups is small. {%- endcomment -%} -{%- assign nav_ordered_groups = nav_ordered_pages +{%- assign nav_number_pages = "" | split: "" -%} +{%- assign nav_string_pages = "" | split: "" -%} +{%- assign nav_order_groups = nav_order_pages | group_by_exp: "item", "item.nav_order | jsonify | slice: 0" -%} +{%- for group in nav_order_groups -%} + {%- if group.name == '"' -%} + {%- assign nav_string_pages = group.items -%} + {%- else -%} + {%- assign nav_number_pages = nav_number_pages | concat: group.items -%} + {%- endif -%} +{%- endfor -%} + +{%- unless nav_number_pages == empty -%} + {%- assign nav_number_pages = nav_number_pages | sort: "nav_order" -%} +{%- endunless -%} + +{%- unless nav_string_pages == empty -%} + {%- if site.nav_sort == 'case_insensitive' -%} + {%- assign nav_string_pages = nav_string_pages | sort_natural: "nav_order" -%} + {%- else -%} + {%- assign nav_string_pages = nav_string_pages | sort: "nav_order" -%} + {%- endif -%} +{%- endunless -%} -{%- assign number_ordered_pages = "" | split: "" -%} -{%- for group in nav_ordered_groups -%} +{%- assign title_number_pages = "" | split: "" -%} +{%- assign title_string_pages = "" | split: "" -%} +{%- assign title_order_groups = title_order_pages + | group_by_exp: "item", "item.title | jsonify | slice: 0" -%} +{%- for group in title_order_groups -%} {%- if group.name == '"' -%} - {%- assign string_ordered_pages = string_ordered_pages | concat: group.items -%} + {%- assign title_string_pages = group.items -%} {%- else -%} - {%- assign number_ordered_pages = number_ordered_pages | concat: group.items -%} + {%- assign title_number_pages = title_number_pages | concat: group.items -%} {%- endif -%} {%- endfor -%} -{%- assign sorted_number_ordered_groups = number_ordered_pages - | sort: "nav_order" | group_by: "nav_order" -%} +{%- unless title_number_pages == empty -%} + {%- assign title_number_pages = title_number_pages | sort: "title" -%} +{%- endunless -%} -{%- comment -%} - Group the string-ordered pages by `nav_order`, if non-nil, and otherwise `title` - (but appending the empty string to a numeric title to convert it to a string). - - Then sort the groups according to the site setting for case (in)sensitivity. -{%- endcomment -%} +{%- unless title_string_pages == empty -%} + {%- if site.nav_sort == 'case_insensitive' -%} + {%- assign title_string_pages = title_string_pages | sort_natural: "title" -%} + {%- else -%} + {%- assign title_string_pages = title_string_pages | sort: "title" -%} + {%- endif -%} +{%- endunless -%} -{%- assign string_ordered_groups = string_ordered_pages - | group_by_exp:"item", "item.nav_order | default: item.title | append: '' " -%} +{%- assign pages_list = nav_number_pages | concat: nav_string_pages + | concat: title_number_pages | concat: title_string_pages -%} -{%- if site.nav_sort == 'case_insensitive' -%} - {%- assign sorted_string_ordered_groups = string_ordered_groups - | sort_natural: "name" -%} -{%- else -%} - {%- assign sorted_string_ordered_groups = string_ordered_groups - | sort:"name" -%} -{%- endif -%} +{%- assign first_level_pages = pages_list + | where_exp: "item", "item.parent == nil" -%} +{%- assign second_level_pages = pages_list + | where_exp: "item", "item.parent != nil" + | where_exp: "item", "item.grand_parent == nil" -%} +{%- assign third_level_pages = pages_list + | where_exp: "item", "item.grand_parent != nil" -%} -{%- assign groups_list = sorted_number_ordered_groups - | concat: sorted_string_ordered_groups -%} +{%- comment -%} + The order of sibling pages in `pages_list` determines the order of display of + links to them in lists of navigation links and in auto-generated TOCs. +{%- endcomment -%} <ul class="nav-list"> - {%- for node_group in groups_list -%} - {%- for node in node_group.items -%} - {%- if node.parent == nil -%} - {%- unless node.nav_exclude -%} +{%- for node in first_level_pages -%} + {%- unless node.nav_exclude -%} <li class="nav-list-item{% if page.collection == include.key and page.url == node.url or page.parent == node.title or page.grand_parent == node.title %} active{% endif %}"> {%- if node.has_children -%} <a href="#" class="nav-list-expander" aria-label="toggle links in {{ node.title }} category"> @@ -76,13 +143,8 @@ {%- endif -%} <a href="{{ node.url | relative_url }}" class="nav-list-link{% if page.url == node.url %} active{% endif %}">{{ node.title }}</a> {%- if node.has_children -%} - {%- assign children_list = "" | split: "" -%} - {%- for parent_group in groups_list -%} - {%- assign children_list = children_list - | concat: parent_group.items - | where: "parent", node.title - | where_exp:"item", "item.grand_parent == nil" -%} - {%- endfor -%} + {%- assign children_list = second_level_pages + | where: "parent", node.title -%} {%- if node.child_nav_order == 'desc' -%} {%- assign children_list = children_list | reverse -%} {%- endif -%} @@ -97,21 +159,17 @@ {%- endif -%} <a href="{{ child.url | relative_url }}" class="nav-list-link{% if page.url == child.url %} active{% endif %}">{{ child.title }}</a> {%- if child.has_children -%} - {%- assign grandchildren_list = "" | split: "" -%} - {%- for grandparent_group in groups_list -%} - {%- assign grandchildren_list = grandchildren_list - | concat: grandparent_group.items - | where: "parent", child.title - | where: "grand_parent", node.title -%} - {%- endfor -%} - {%- if node.child_nav_order == 'desc' -%} - {%- assign grandchildren_list = grandchildren_list | reverse -%} + {%- assign grand_children_list = third_level_pages + | where: "parent", child.title + | where: "grand_parent", node.title -%} + {%- if child.child_nav_order == 'desc' -%} + {%- assign grand_children_list = grand_children_list | reverse -%} {%- endif -%} <ul class="nav-list"> - {%- for grandchild in grandchildren_list -%} - {%- unless grandchild.nav_exclude -%} - <li class="nav-list-item {% if page.url == grandchild.url %} active{% endif %}"> - <a href="{{ grandchild.url | relative_url }}" class="nav-list-link{% if page.url == grandchild.url %} active{% endif %}">{{ grandchild.title }}</a> + {%- for grand_child in grand_children_list -%} + {%- unless grand_child.nav_exclude -%} + <li class="nav-list-item {% if page.url == grand_child.url %} active{% endif %}"> + <a href="{{ grand_child.url | relative_url }}" class="nav-list-link{% if page.url == grand_child.url %} active{% endif %}">{{ grand_child.title }}</a> </li> {%- endunless -%} {%- endfor -%} @@ -123,65 +181,53 @@ </ul> {%- endif -%} </li> - {%- endunless -%} - {%- endif -%} - {%- endfor -%} - {%- endfor -%} - {%- assign nav_external_links = site.nav_external_links -%} - {%- for node in nav_external_links -%} - <li class="nav-list-item external"> - <a href="{{ node.url | absolute_url }}" class="nav-list-link external"> - {{ node.title }} - {% unless node.hide_icon %}<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title"><use xlink:href="#svg-external-link"></use></svg>{% endunless %} - </a> - </li> - {%- endfor -%} + {%- endunless -%} +{%- endfor -%} +{%- assign nav_external_links = site.nav_external_links -%} +{%- for node in nav_external_links -%} + <li class="nav-list-item external"> + <a href="{{ node.url | absolute_url }}" class="nav-list-link external"> + {{ node.title }} + {% unless node.hide_icon %}<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title"><use xlink:href="#svg-external-link"></use></svg>{% endunless %} + </a> + </li> +{%- endfor -%} </ul> -{%- if page.collection == include.key -%} +{%- comment -%} + `page.collection` is the name of the Jekyll collection that contains the page, + if any, and otherwise nil. Similarly for `include.key`. + + If the current page is in the collection (if any) whose navigation is currently + being generated, the following code sets `first_level_url` to the URL used in + the page's top-level breadcrumb (if any), and `second_level_url` to that used + in the page's second-level breadcrumb (if any). + + For pages with children, the code also sets `toc_list` to the list of child pages. +{%- endcomment -%} - {%- for node_group in groups_list -%} - {%- for node in node_group.items -%} - {%- if node.parent == nil -%} - {%- if page.grand_parent == node.title - or page.parent == node.title - and page.grand_parent == nil -%} +{%- if page.collection == include.key -%} + {%- for node in first_level_pages -%} + {%- if page.grand_parent == node.title or page.parent == node.title and page.grand_parent == nil -%} {%- assign first_level_url = node.url | relative_url -%} {%- endif -%} {%- if node.has_children -%} - {%- assign children_list = "" | split: "" -%} - {%- for parent_group in groups_list -%} - {%- assign children_list = children_list | concat: - parent_group.items | where: "parent", node.title -%} - {%- endfor -%} - {%- if node.child_nav_order == 'desc' -%} - {%- assign children_list = children_list | reverse -%} - {%- endif -%} + {%- assign children_list = second_level_pages | where: "parent", node.title -%} {%- for child in children_list -%} {%- if child.has_children -%} - {%- if page.url == child.url - or page.parent == child.title - and page.grand_parent == child.parent -%} + {%- if page.url == child.url or page.parent == child.title and page.grand_parent == child.parent -%} {%- assign second_level_url = child.url | relative_url -%} {%- endif -%} {%- endif -%} {%- endfor -%} {%- endif -%} - {%- endif -%} - {%- endfor -%} {%- endfor -%} - - {% if page.has_children == true and page.has_toc != false %} - {%- assign toc_list = "" | split: "" -%} - {%- for parent_group in groups_list -%} - {%- assign toc_list = toc_list - | concat: parent_group.items - | where: "parent", page.title - | where: "grand_parent", page.parent -%} - {%- endfor -%} - {%- if node.child_nav_order == 'desc' -%} + {%- if page.has_children == true and page.has_toc != false -%} + {%- assign toc_list = pages_list + | where: "parent", page.title + | where: "grand_parent", page.parent -%} + {%- if page.child_nav_order == "desc" -%} {%- assign toc_list = toc_list | reverse -%} {%- endif -%} {%- endif -%} - {%- endif -%} diff --git a/docs/navigation-structure.md b/docs/navigation-structure.md index 7cee2e87aa9b83453d6865a21b55de3dce5e0982..d127369ecb998d92fc8a7992ffa67716c8ea2e51 100644 --- a/docs/navigation-structure.md +++ b/docs/navigation-structure.md @@ -44,11 +44,10 @@ nav_order: 4 The parameter values determine the order of the top-level pages, and of child pages with the same parent. You can reuse the same parameter values (e.g., integers starting from 1) for the child pages of different parents. -The parameter values can be numbers (integers, floats) and/or strings. When you omit `nav_order` parameters, they default to the titles of the pages, which are ordered alphabetically. Pages with numerical `nav_order` parameters always come before those with strings or default `nav_order` parameters. If you want to make the page order independent of the page titles, you can set explicit `nav_order` parameters on all pages. +The parameter values can be numbers (integers, floats) and/or strings. Pages with numerical `nav_order` parameters always come before those with string `nav_order` parameters. When you omit `nav_order` parameters, they default to the titles of the pages. If you want to make the page order independent of the page titles, you can set explicit `nav_order` parameters on all pages. All pages with explicit `nav_order` parameters +come before all pages ordered by their `title` values. -By default, all Capital letters come before all lowercase letters; you can add `nav_sort: case_insensitive` in the configuration file to ignore the case. Enclosing strings in quotation marks is optional. - -> _Note for users of previous versions:_ `nav_sort: case_insensitive` previously affected the ordering of numerical `nav_order` parameters: e.g., `10` came before `2`. Also, all pages with explicit `nav_order` parameters previously came before all pages with default parameters. Both were potentially confusing, and they have now been eliminated. +By default, all Capital letters come before all lowercase letters; you can add `nav_sort: case_insensitive` in the configuration file to ignore the case. Enclosing strings in (single or double) quotation marks is optional. Numeric values are not enclosed in quotation marks, e.g., `42`, `-1.0`; numbers in quotation marks are lexicographically ordered, so `"10"` comes before `"2"`, for example. --- @@ -70,7 +69,7 @@ nav_exclude: true The `nav_exclude` parameter does not affect the [auto-generating list of child pages](#auto-generating-table-of-contents), which you can use to access pages excluded from the main navigation. -Pages with no `title` are automatically excluded from the navigation. +Pages with no `title` are automatically excluded from the main navigation. --- @@ -229,6 +228,9 @@ This would create the following navigation structure: +-- .. ``` +{: .note } +Currently, the navigation structure is limited to 3 levels: grandchild pages cannot themselves have child pages. + --- ## Auxiliary Links