diff --git a/_config_dev.yml b/_config_dev.yml new file mode 100644 index 0000000000..1f92be0c4f --- /dev/null +++ b/_config_dev.yml @@ -0,0 +1,5 @@ +# For local development and testing, use the option +# --config _config.yml,_config_dev.yml +# See https://stackoverflow.com/a/27400343 + +url: http://localhost:4000/ diff --git a/_include_tests.yml b/_include_tests.yml new file mode 100644 index 0000000000..6afa8e1425 --- /dev/null +++ b/_include_tests.yml @@ -0,0 +1,22 @@ +# Configuration addition to include regression tests +# Use jekyll option --config _config.yml,_include_tests.yml + +collections: + tests: + permalink: "/:collection/:path:output_ext" + output: true + +just_the_docs: + collections: + tests: + name: Tests + nav_exclude: false + search_exclude: true + +defaults: + - + scope: + path: "" # an empty string here means all files in the project + type: "tests" + values: + layout: "default" diff --git a/_includes/nav/children.html b/_includes/nav/children.html new file mode 100644 index 0000000000..32ae65d31c --- /dev/null +++ b/_includes/nav/children.html @@ -0,0 +1,41 @@ +{%- comment -%} + {%- include nav/children.html + node = node + path = string + in_section = value_or_nil -%} + assigns to `nav_children` an unsorted array containing children of `node`, + including `child` when all its specified disambiguation fields match: + - `child.grand_parent == node.parent`, + - `child.in_section == in_section`, and + - `path contains child.ancestor`. +{%- endcomment -%} + +{%- assign nav_candidates = nav_parenthood + | where: "name", include.node.title | map: "items" | first -%} +{%- assign nav_children = "" | split: "X" -%} + +{%- for child in nav_candidates -%} + {%- assign nav_child_ok = true -%} + + {%- if child.grand_parent and child.grand_parent != include.node.parent -%} + {%- assign nav_child_ok = false -%} + {%- endif -%} + + {%- if child.in_section and child.in_section != include.in_section -%} + {%- assign nav_child_ok = false -%} + {%- endif -%} + +{%- capture nav_newline %} +{% endcapture -%} + + {%- if child.ancestor -%} + {%- assign nav_child_ancestor_title = nav_newline | append: child.ancestor | append: nav_newline -%} + {%- unless include.path contains nav_child_ancestor_title -%} + {%- assign nav_child_ok = false -%} + {%- endunless -%} + {%- endif -%} + + {%- if nav_child_ok -%} + {%- assign nav_children = nav_children | push: child -%} + {%- endif -%} +{%- endfor -%} diff --git a/_includes/nav/collection.html b/_includes/nav/collection.html new file mode 100644 index 0000000000..7a04a1261d --- /dev/null +++ b/_includes/nav/collection.html @@ -0,0 +1,77 @@ +{%- comment -%} + {%- include nav/collection.html + pages = array + key = name_or_nil -%} + outputs the main navigation links for `pages`. It also assigns arrays of pages + to variables inspected by `nav/crumbs` and `nav/toc`. + + When `key` is `nil`, `pages` is `site.html_pages`; otherwise `key` is the name + of a collection, and `pages` are the pages of that collection. (Pages without + a `title` are automatically excluded in the former case, but Jekyll provides + default titles for pages in collections, so their untitled pages are not + automatically excluded.) + + `collection` first groups `pages` by their `parent` fields, creating an + unsorted array of hashes, assigned to `nav_parenthood`. + + When `pages` contains the current `page`, `collection` calls `nav/page` to + search for the navigation path to `page`, then: + - assigns to `nav_page_path` the string of page titles leading to `page`, + - assigns to `nav_page_ancestors` the array of ancestors of `page`, and + - assigns to `nav_page_children` the array of children of `page`. + + When pages are stored in folders whose nesting corresponds to the parent + relation, only nodes whose folder path is a prefix of `page.path` need to be + searched. For larger sites, using folder paths to direct searching for `page` + can exponentially reduce the build time. To avoid dependence on assumptions + about the folders used to store pages, however, exhaustive search is needed + as a fall-back when directed search fails. + + Finally, `collection` outputs the main navigation links for `pages`. + + The presence of navigation expanders requires the navigation links for the + entire site to be output on every page. To reduce build time, the navigation + links for inactive top-level nodes are cached. (It would be possible to cache + also the navigation links for inactive lower-level nodes, but that does not + reduce the build time signficantly.) +{%- endcomment -%} + +{%- assign nav_parenthood = include.pages + | where_exp: "item", "item.title != nil" | group_by: "parent" -%} + +{%- assign nav_top_nodes = nav_parenthood + | where_exp: "item", "item.name == ''" | map: "items" | first -%} + +{%- assign nav_page_path = nil -%} + +{%- if include.key == nil or include.key == page.collection -%} + + {%- assign nav_page_dir = page.path | split: "/" | pop | join: "/" -%} + {%- assign nav_ancestors = "" | split: "X" -%} + +{%- capture nav_newline %} +{% endcapture -%} + + {%- include nav/page.html + direct = true + nodes = nav_top_nodes + ancestors = nav_ancestors + path = nav_newline + in_section = nil -%} + + {%- unless nav_page_path -%} + {%- include nav/page.html + direct = false + nodes = nav_top_nodes + ancestors = nav_ancestors + path = nav_newline + in_section = nil -%} + {%- endunless -%} + +{%- endif -%} + +{%- include nav/links.html + nodes = nav_top_nodes + path = nav_newline + in_section = nil + pages = include.pages -%} diff --git a/_includes/nav/crumbs.html b/_includes/nav/crumbs.html new file mode 100644 index 0000000000..dd7ca36bb3 --- /dev/null +++ b/_includes/nav/crumbs.html @@ -0,0 +1,19 @@ +{%- comment -%} + {%- include nav/crumbs.html + nodes = array -%} + outputs breadcrumb navigation links for the nodes, and the title of the + current page. +{%- endcomment -%} + + diff --git a/_includes/nav/links.html b/_includes/nav/links.html new file mode 100644 index 0000000000..e60f2b68ca --- /dev/null +++ b/_includes/nav/links.html @@ -0,0 +1,38 @@ +{%- comment -%} + {%- include nav/links.html + nodes = array + path = string + in_section = value_or_nil + pages = array -%} + outputs links for non-excluded nodes and their children. The links for an + inactive node are independent of the path to the current page, but only those + for top-level nodes are cached. +{%- endcomment -%} + +{%- include nav/sorted.html + nodes = include.nodes -%} + + diff --git a/_includes/nav/links_inactive.html b/_includes/nav/links_inactive.html new file mode 100644 index 0000000000..c3d04be1a7 --- /dev/null +++ b/_includes/nav/links_inactive.html @@ -0,0 +1,26 @@ +{%- comment -%} + {%- include nav/links_inactive.html + nodes = array + path = string + in_section = value_or_nil + pages = array -%} + outputs links for inactive nodes and their children. +{%- endcomment -%} + +{%- include nav/sorted.html + nodes = include.nodes -%} + + diff --git a/_includes/nav/main.html b/_includes/nav/main.html new file mode 100644 index 0000000000..436b2aff65 --- /dev/null +++ b/_includes/nav/main.html @@ -0,0 +1,40 @@ +{%- comment -%} + {%- include nav/main.html -%} + outputs links for the main navigation. It also locates the current page in the + navigation hierarchy, and assigns to the variables `nav_page_ancestors`, + `nav_page_path`, and `nav_page_children`. + + `nav/main` first outputs the main navigation for pages not in any collection. + It then outputs the main navigation for the pages in each collection (preceded + by the name of the collection, unless all pages are in a single collection.) +{%- endcomment -%} + +{%- assign pages_top_size = site.html_pages + | where_exp:"item", "item.title != nil" + | where_exp:"item", "item.parent == nil" + | where_exp:"item", "item.nav_exclude != true" + | size -%} +{%- if pages_top_size > 0 -%} + {%- include nav/collection.html + pages = site.html_pages + key = nil -%} +{%- endif -%} + +{%- if site.just_the_docs.collections -%} + {%- assign collections_size = site.just_the_docs.collections | size -%} + {%- for collection_entry in site.just_the_docs.collections -%} + + {%- assign collection_key = collection_entry[0] -%} + {%- assign collection_value = collection_entry[1] -%} + {%- assign collection = site[collection_key] -%} + {%- if collection_value.nav_exclude != true -%} + {%- if collections_size > 1 or pages_top_size > 0 -%} + + {%- endif -%} + {%- include nav/collection.html + pages = collection + key = collection_key -%} + {%- endif -%} + + {%- endfor -%} +{%- endif -%} diff --git a/_includes/nav/node.html b/_includes/nav/node.html new file mode 100644 index 0000000000..2ad21901cc --- /dev/null +++ b/_includes/nav/node.html @@ -0,0 +1,45 @@ +{%- comment -%} + {%- include nav/node.html + node = node + path = string + in_section = value_or_nil + pages = array + active = bool -%} + outputs links for the node and its children. +{%- endcomment -%} + +{%- if node.section_id -%} + {%- assign nav_section = node.section_id -%} +{%- else -%} + {%- assign nav_section = include.in_section -%} +{%- endif -%} + +{%- include nav/children.html + node = include.node + path = include.path + in_section = nav_section -%} + + diff --git a/_includes/nav/node_inactive.html b/_includes/nav/node_inactive.html new file mode 100644 index 0000000000..e9373de350 --- /dev/null +++ b/_includes/nav/node_inactive.html @@ -0,0 +1,47 @@ +{%- comment -%} + {%- include nav/node_inactive.html + node = node + path = string + in_section = value_or_nil + pages = array + cached = bool -%} + outputs links for the inactive node and its children. The +{%- endcomment -%} + +{%- if include.cached -%} + {%- assign nav_parenthood = include.pages + | where_exp: "item", "item.title != nil" + | group_by: "parent" -%} +{%- endif -%} + +{%- if node.section_id -%} + {%- assign nav_section = node.section_id -%} +{%- else -%} + {%- assign nav_section = include.in_section -%} +{%- endif -%} + +{%- include nav/children.html + node = node + path = include.path + in_section = nav_section -%} + + diff --git a/_includes/nav/page.html b/_includes/nav/page.html new file mode 100644 index 0000000000..cd759dbc20 --- /dev/null +++ b/_includes/nav/page.html @@ -0,0 +1,69 @@ +{%- comment -%} + {%- include nav/page.html + direct = bool + nodes = array + ancestors = array + path = string + in_section = value_or_nil -%} + searches for the location of the current page in the part of the navigation + for the `nodes` parameter, using `nav_parenthood` for finding child pages. + When the current page is located, `nav/page` assigns values to + `nav_page_ancestors`, `nav_page_path`, and `nav_page_children`. + + When `direct` is false, the search is exhaustive; otherwise it is directed by + the directory hierarchy. (An outer `docs` directory is ignored, which supports + sites where documentation pages are kept separately from source code pages.) +{%- endcomment -%} + +{%- unless nav_page_path -%} + {%- for node in include.nodes -%} + + {%- assign node_dir_list = node.path | split: "/" -%} + {%- if node_dir_list.first == "docs" -%} + {%- assign node_dir = node_dir_list | shift | pop | join: "/" -%} + {%- else -%} + {%- assign node_dir = node_dir_list | pop | join: "/" -%} + {%- endif -%} + + {%- if nav_page_dir contains node_dir or include.direct == false -%} + + {%- if node.section_id -%} + {%- assign nav_section = node.section_id -%} + {%- else -%} + {%- assign nav_section = include.in_section -%} + {%- endif -%} + + {%- include nav/children.html + node = node + path = include.path + in_section = nav_section + parenthood = nav_parenthood -%} + +{%- capture nav_newline %} +{% endcapture -%} + + {%- assign nav_path = include.path | append: node.title | append: nav_newline -%} + + {%- if node.url == page.url -%} + {%- assign nav_page_ancestors = include.ancestors -%} + {%- assign nav_page_path = nav_path -%} + {%- assign nav_direct = true -%} + {%- include nav/sorted.html + nodes = nav_children -%} + {%- assign nav_page_children = nav_sorted -%} + {%- break -%} + {%- endif -%} + + {%- if nav_children.size >= 1 -%} + {%- assign nav_children_ancestors = include.ancestors | push: node -%} + {%- include nav/page.html + direct = include.direct + nodes = nav_children + ancestors = nav_children_ancestors + path = nav_path + in_section = nav_section -%} + {%- endif -%} + + {%- endif -%} + {%- endfor -%} +{%- endunless -%} diff --git a/_includes/nav/sorted.html b/_includes/nav/sorted.html new file mode 100644 index 0000000000..b740301d1c --- /dev/null +++ b/_includes/nav/sorted.html @@ -0,0 +1,64 @@ +{%- comment -%} + {%- include nav/sorted.html + nodes = nodes -%} + assigns to `nav_sorted` an array formed by sorting `nodes`. + + 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. + + Here, numbers are sorted by their values, and come before all strings. + An omitted `nav_order` value is equivalent to the node's `title` value + (except that a numerical `title` value is treated as a string). + + The case-sensitivity of string sorting is determined by `site.nav_sort`. + + Sorting an array with hundreds of nodes on each page of a large site can lead + to long build times Moreover, the order of children of different parents is + irrelevant. Efficiency is gained by sorting each set of children separately. +{%- endcomment -%} + +{%- assign nav_string_ordered_pages = include.nodes + | where_exp:"item", "item.nav_order == nil" -%} +{%- assign nav_ordered_pages = include.nodes + | where_exp:"item", "item.nav_order != nil" -%} + +{%- comment -%} + The `nav_ordered_pages` have to be added to `nav_number_ordered_pages` and + `nav_string_ordered_pages`, depending on the `nav_order` value type. + The first character of the `jsonify` result is `"` only for strings. +{%- endcomment -%} + +{%- assign nav_ordered_groups = nav_ordered_pages + | group_by_exp:"item", "item.nav_order | jsonify | slice: 0" -%} +{%- assign nav_number_ordered_pages = "" | split:"X" -%} +{%- for group in nav_ordered_groups -%} + {%- if group.name == '"' -%} + {%- assign nav_string_ordered_pages = nav_string_ordered_pages | concat: group.items -%} + {%- else -%} + {%- assign nav_number_ordered_pages = nav_number_ordered_pages | concat: group.items -%} + {%- endif -%} +{%- endfor -%} + +{%- assign nav_sorted_number_ordered_pages = nav_number_ordered_pages | sort:"nav_order" -%} + +{%- comment -%} + The `nav_string_ordered_pages` have to be sorted by `nav_order`, and otherwise + `title` (appending the empty string to a numeric title converts it to a string). + After grouping them by those values, the groups are sorted, then the items + of each group are concatenated. +{%- endcomment -%} + +{%- assign nav_string_ordered_groups = nav_string_ordered_pages + | group_by_exp:"item", "item.nav_order | default: item.title | append:''" -%} +{%- if site.nav_sort == 'case_insensitive' -%} + {%- assign nav_sorted_string_ordered_groups = nav_string_ordered_groups | sort_natural:"name" -%} +{%- else -%} + {%- assign nav_sorted_string_ordered_groups = nav_string_ordered_groups | sort:"name" -%} +{%- endif -%} +{%- assign nav_sorted_string_ordered_pages = "" | split:"X" -%} +{%- for group in nav_sorted_string_ordered_groups -%} + {%- assign nav_sorted_string_ordered_pages = nav_sorted_string_ordered_pages | concat: group.items -%} +{%- endfor -%} + +{%- assign nav_sorted = nav_sorted_number_ordered_pages | concat: nav_sorted_string_ordered_pages -%} diff --git a/_includes/nav/toc.html b/_includes/nav/toc.html new file mode 100644 index 0000000000..00d40763f6 --- /dev/null +++ b/_includes/nav/toc.html @@ -0,0 +1,21 @@ +{%- comment -%} + {%- include nav/toc.html + nodes = array -%} + outputs a table of contents with navigation links to the nodes, unless empty. + The heading for the table can be configured by setting `site.toc_heading`. +{%- endcomment -%} + +{%- if include.nodes.size >= 1 -%} +
+

{{ site.toc_heading | default: "Table of contents" }}

+ +{%- endif -%} diff --git a/_layouts/default.html b/_layouts/default.html index c86d973357..639ba51a5e 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -73,34 +73,7 @@ {%- endfor -%} {%- endif -%} - {% if site.just_the_docs.collections %} - {% assign collections_size = site.just_the_docs.collections | size %} - {% for collection_entry in site.just_the_docs.collections %} - {% assign collection_key = collection_entry[0] %} - {% assign collection_value = collection_entry[1] %} - {% assign collection = site[collection_key] %} - {% if collection_value.nav_exclude != true %} - {% if collections_size > 1 or pages_top_size > 0 %} - {% if collection_value.nav_fold == true %} - - {% else %} - - {% include nav.html pages=collection key=collection_key %} - {% endif %} - {% else %} - {% include nav.html pages=collection key=collection_key %} - {% endif %} - {% endif %} - {% endfor %} - {% endif %} + {% include nav/main.html %} {% capture nav_footer_custom %} @@ -157,21 +130,9 @@ {% endif %}
- {% unless page.url == "/" %} - {% if page.parent %} - - {% endif %} - {% endunless %} + {% if page.parent and page.url != "/" %} + {% include nav/crumbs.html nodes=nav_page_ancestors %} + {% endif %}
{% if site.heading_anchors != false %} {% include vendor/anchor_headings.html html=content beforeHeading="true" anchorBody="" anchorClass="anchor-heading" anchorAttrs="aria-labelledby=\"%html_id%\"" %} @@ -179,17 +140,9 @@ {{ content }} {% endif %} - {% if page.has_children == true and page.has_toc != false %} -
- {% include toc_heading_custom.html %} - - {% endif %} + {% unless page.has_toc == false %} + {% include nav/toc.html nodes=nav_page_children %} + {% endunless %} {% capture footer_custom %} {%- include footer_custom.html -%} diff --git a/_nav_cache_false.yml b/_nav_cache_false.yml new file mode 100644 index 0000000000..91d80b6ed1 --- /dev/null +++ b/_nav_cache_false.yml @@ -0,0 +1,4 @@ +# Configuration addition to disable caching of navigation links +# Use jekyll option --config _config.yml,_nav_cache_false.yml + +nav_cache: false diff --git a/_test_collection_1/child.md b/_test_collection_1/child.md new file mode 100644 index 0000000000..b1393c9306 --- /dev/null +++ b/_test_collection_1/child.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Child Test +parent: Parent Test +--- + +# Child Test + +This is a child page in Test Collection 1. + +The breadcrumb link above should lead to a page in the same collection. diff --git a/_test_collection_1/index.md b/_test_collection_1/index.md new file mode 100644 index 0000000000..7990cc9cf3 --- /dev/null +++ b/_test_collection_1/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Parent Test +has_children: true +--- + +# Parent Test + +This is a top-level page in Test Collection 1. + +The link below should lead to a page in the same collection. diff --git a/_test_collection_2/child.md b/_test_collection_2/child.md new file mode 100644 index 0000000000..0c1d611417 --- /dev/null +++ b/_test_collection_2/child.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Child Test +parent: Parent Test +--- + +# Child Test + +This is a child page in Test Collection 2. + +The breadcrumb link above should lead to a page in the same collection. diff --git a/_test_collection_2/index.md b/_test_collection_2/index.md new file mode 100644 index 0000000000..c6edf19950 --- /dev/null +++ b/_test_collection_2/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Parent Test +has_children: true +--- + +# Parent Test + +This is a top-level page in Test Collection 2. + +The link below should lead to a page in the same collection.