From 8e38759613c027477764f2f60fbf5ed3550fbca0 Mon Sep 17 00:00:00 2001 From: Peter Mosses <18308236+pdmosses@users.noreply.github.com> Date: Tue, 9 May 2023 17:57:26 +0200 Subject: [PATCH] Fix liquid variable leakage in navigation components (#1243) * Refactor nav, breadcrumbs, children_nav Fix #1118 Improve the modularity of building the nav-panel, breadcrumbs, and children-nav by making them independent. This also significantly simplifies the Liquid code. * Fix order of breadcrumbs * Update breadcrumbs.html Revert inclusion of single breadcrumb for top-level pages. * Update breadcrumbs.html * Update children_nav.html Revert to the previous layout in the HTML, to allow the use of `diff` to check the built site. * Update minimal.html Remove the previously required workaround involving `nav.html`. * Add docs pages about layouts The aim of the initial version of these docs pages is to illustrate the difference between the default and minimal layouts. * Update CHANGELOG.md --- CHANGELOG.md | 4 +- _includes/components/breadcrumbs.html | 58 ++++++-- _includes/components/children_nav.html | 34 ++++- _includes/nav.html | 191 +++---------------------- _includes/sorted_pages.html | 95 ++++++++++++ _layouts/default.html | 2 +- _layouts/minimal.html | 28 +--- docs/layout/layout.md | 40 ++++++ docs/layout/minimal/default-child.md | 8 ++ docs/layout/minimal/minimal-child.md | 8 ++ docs/layout/minimal/minimal.md | 12 ++ 11 files changed, 263 insertions(+), 217 deletions(-) create mode 100644 _includes/sorted_pages.html create mode 100644 docs/layout/layout.md create mode 100644 docs/layout/minimal/default-child.md create mode 100644 docs/layout/minimal/minimal-child.md create mode 100644 docs/layout/minimal/minimal.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e1e1d..f18e399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,12 +20,14 @@ This website includes docs for some new features that are not available in `v0.5 Code changes to `main` that are *not* in the latest release: -- N/A +- Fixed: liquid variable leakage in navigation components by [@pdmosses] in [#1243] Docs changes in `main` that are *not* in the latest release: - N/A +[#1243]: https://github.com/just-the-docs/just-the-docs/pull/1243 + ## Release v0.5.1 Hi all, this is a very small minor patch release that has two small behavioral bugfixes: fixing a regression introduced in `v0.5.0` on Safari versions `<16.4` (broken media query), and the copy code button providing incorrect feedback in insecure browser contexts. This should be a smooth upgrade with no breaking changes. diff --git a/_includes/components/breadcrumbs.html b/_includes/components/breadcrumbs.html index f1bc488..82ad6bd 100644 --- a/_includes/components/breadcrumbs.html +++ b/_includes/components/breadcrumbs.html @@ -1,15 +1,43 @@ -{% unless page.url == "/" %} - {% if page.parent %} - - {% endif %} -{% endunless %} +{%- comment -%} + Include as: {%- include components/breadcrumbs.html -%} + Depends on: page, site. + Results in: HTML for the breadcrumbs component. + Overwrites: + pages_list, parent_page, grandparent_page. +{%- endcomment -%} + +{%- if page.url != "/" and page.parent -%} + + {%- assign pages_list = site[page.collection] + | default: site.html_pages + | where_exp: "item", "item.title != nil" + | where_exp: "item", "item.has_children != nil" -%} + + {%- if page.grand_parent -%} + {%- assign parent_page = pages_list + | where: "title", page.parent + | where: "parent", page.grand_parent + | first -%} + {%- assign grandparent_page = pages_list + | where: "title", page.grand_parent + | first -%} + {%- else -%} + {%- assign parent_page = pages_list + | where: "title", page.parent + | where_exp: "item", "item.parent == nil" + | first -%} + {%- endif -%} + + + +{%- endif -%} diff --git a/_includes/components/children_nav.html b/_includes/components/children_nav.html index e76f98d..ce2482a 100644 --- a/_includes/components/children_nav.html +++ b/_includes/components/children_nav.html @@ -1,9 +1,33 @@ +{%- comment -%} + Include as: {%- include components/children_nav.html -%} + Depends on: page, site. + Results in: HTML for the children-navigation component. + Includes: + sorted_pages.html + toc_heading_custom.html + Overwrites: + child_pages. +{%- endcomment -%} + +{%- if page.has_children == true and page.has_toc != false -%} + {%- assign child_pages = site[page.collection] + | default: site.html_pages + | where: "parent", page.title + | where: "grand_parent", page.parent -%} + + {%- include sorted_pages.html pages = child_pages -%} + + {%- if page.child_nav_order == 'desc' or page.child_nav_order == 'reversed' -%} + {%- assign sorted_pages = sorted_pages | reverse -%} + {%- endif -%} +{%- endif -%} +
{% include toc_heading_custom.html %} diff --git a/_includes/nav.html b/_includes/nav.html index e80dfcd..422f833 100644 --- a/_includes/nav.html +++ b/_includes/nav.html @@ -1,137 +1,31 @@ {%- comment -%} - 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 `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.) + Include as: {%- include nav.html pages = pages key = key -%} + Depends on: include.pages, include.key, page, site. + Results in: HTML for the navigation panel. + Includes: + sorted_pages.html + Overwrites: + nav_pages, first_level_pages, second_level_pages, third_level_pages, + node, children_list, child, grand_children_list, grand_child. {%- endcomment -%} -{%- assign title_pages = include.pages - | where_exp: "item", "item.title != nil" -%} +{%- assign nav_pages = include.pages + | where_exp: "item", "item.title != nil" + | where_exp: "item", "item.nav_exclude != true" -%} + +{%- include sorted_pages.html pages = nav_pages -%} + +{%- assign first_level_pages = sorted_pages + | where_exp: "item", "item.parent == nil" -%} +{%- assign second_level_pages = sorted_pages + | where_exp: "item", "item.parent != nil" + | where_exp: "item", "item.grand_parent == nil" -%} +{%- assign third_level_pages = sorted_pages + | where_exp: "item", "item.grand_parent != nil" -%} {%- 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 -%} - -{%- unless title_pages == empty -%} - {%- 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 -%} -{%- endunless -%} - -{%- 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" -%} - -{%- comment -%} - Divide the arrays of `nav_order_pages` and `title_order_pages` according to - the type of value. - - 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_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 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 title_string_pages = group.items -%} - {%- else -%} - {%- assign title_number_pages = title_number_pages | concat: group.items -%} - {%- endif -%} -{%- endfor -%} - -{%- unless title_number_pages == empty -%} - {%- assign title_number_pages = title_number_pages | sort: "title" -%} -{%- endunless -%} - -{%- 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 pages_list = nav_number_pages | concat: nav_string_pages - | concat: title_number_pages | concat: title_string_pages -%} - -{%- 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" -%} - -{%- 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. + The order of sibling pages in `sorted_pages` determines the order of display of + links to them in lists of navigation links. Note that Liquid evaluates conditions from right to left (and it does not allow the use of parentheses). Some conditions are not so easy to express clearly... @@ -210,42 +104,3 @@ {%- endunless -%} {%- endfor -%} - -{%- 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, - reversing the order if needed. -{%- endcomment -%} - -{%- 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 = 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 -%} - {%- assign second_level_url = child.url | relative_url -%} - {%- endif -%} - {%- endif -%} - {%- endfor -%} - {%- endif -%} - {%- endfor -%} - {%- if page.has_children == true and page.has_toc != false -%} - {%- assign toc_list = pages_list - | where: "parent", page.title - | where_exp: "item", "item.grand_parent == page.parent" -%} - {%- if page.child_nav_order == 'desc' or page.child_nav_order == 'reversed' -%} - {%- assign toc_list = toc_list | reverse -%} - {%- endif -%} - {%- endif -%} -{%- endif -%} diff --git a/_includes/sorted_pages.html b/_includes/sorted_pages.html new file mode 100644 index 0000000..0870aab --- /dev/null +++ b/_includes/sorted_pages.html @@ -0,0 +1,95 @@ +{%- comment -%} + Include as: {%- include sorted_pages.html pages = pages -%} + Depends on: include.pages. + Assigns to: sorted_pages. + Overwrites: + nav_order_pages, title_order_pages, + nav_number_pages, nav_string_pages, nav_order_groups, group, + title_number_pages, title_string_pages, title_order_groups. +{%- endcomment -%} + +{%- comment -%} + 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 `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 -%} + +{%- assign nav_order_pages = include.pages + | where_exp: "item", "item.nav_order != nil" -%} +{%- assign title_order_pages = include.pages + | where_exp: "item", "item.nav_order == nil" -%} + +{%- comment -%} + Divide the arrays of `nav_order_pages` and `title_order_pages` according to + the type of value. + + 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_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 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 title_string_pages = group.items -%} + {%- else -%} + {%- assign title_number_pages = title_number_pages | concat: group.items -%} + {%- endif -%} +{%- endfor -%} + +{%- unless title_number_pages == empty -%} + {%- assign title_number_pages = title_number_pages | sort: "title" -%} +{%- endunless -%} + +{%- 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 sorted_pages = nav_number_pages + | concat: nav_string_pages + | concat: title_number_pages + | concat: title_string_pages -%} diff --git a/_layouts/default.html b/_layouts/default.html index 3bc0e0a..6be391e 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -22,7 +22,7 @@ layout: table_wrappers {% endif %} {% if page.has_children == true and page.has_toc != false %} - {% include components/children_nav.html toc_list=toc_list %} + {% include components/children_nav.html %} {% endif %} {% include components/footer.html %} diff --git a/_layouts/minimal.html b/_layouts/minimal.html index 0fb9511..854dbd0 100644 --- a/_layouts/minimal.html +++ b/_layouts/minimal.html @@ -9,32 +9,6 @@ layout: table_wrappers Skip to main content {% include icons/icons.html %} - {% comment %} - This is a bandaid fix to properly render breadcrumbs; as of now, there is some variable leakage between the sidebar component (which computes parents, grandparents) and the breadcrumbs component. We plan to remove this in a future release to deduplicate code. - - For more context, see https://github.com/just-the-docs/just-the-docs/pull/1058#discussion_r1057014053 - {% endcomment %} - {% capture nav %} - {% 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.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 %} - {% include nav.html pages=collection key=collection_key %} - {% endif %} - {% endfor %} - {% endif %} - {% endcapture %}
{% include components/breadcrumbs.html %}
@@ -45,7 +19,7 @@ layout: table_wrappers {% endif %} {% if page.has_children == true and page.has_toc != false %} - {% include components/children_nav.html toc_list=toc_list %} + {% include components/children_nav.html %} {% endif %} {% include components/footer.html %} diff --git a/docs/layout/layout.md b/docs/layout/layout.md new file mode 100644 index 0000000..6bf3de9 --- /dev/null +++ b/docs/layout/layout.md @@ -0,0 +1,40 @@ +--- +title: Layout +layout: default +nav_order: 4.5 +has_children: true +--- + +# Layout + +You specify the layout for a page in its [front matter]. Just the Docs has a `default` layout with a sidebar, used for almost all pages in the theme docs, and a `minimal` layout that omits the sidebar. +{: .fs-6 .fw-300 } + +## The layout concept + +See the [Jekyll docs page about layouts] for an explanation of the general idea of layouts and how to specify them. + +You can use [Jekyll's front matter defaults] to specify the same layout for many pages. + +## The `default` layout + +This page uses the default layout. + +It is a *responsive* layout: on medium and larger width displays, it displays a sidebar, including a navigation panel; on smaller width displays, the sidebar is automatically hidden under a button. + +Each child (and grandchild) page of a top-level page has so-called *breadcrumbs*: links to its parent (and grandparent) pages. It shows the breadcrumbs above the main content of the page. + +Each page that has child pages generally has a list of links to those pages (you can suppress it by `has_toc: false` in the front matter). It shows the list as a *table of contents* below the main content. + +## The `minimal` layout + +A child and grandchild page of this page use the minimal layout. This differs from the default layout by omitting the sidebar -- and thereby also the navigation panel. To navigate between pages with the minimal layout, you can use the breadcrumbs and the tables of contents. + +## Other layouts + +Just the Docs has further layouts: `about`, `home`, `page`, and `post`. Currently, they are all based on the `default` layout. See the [Jekyll docs about inheritance] for how to customize them. + +[front matter]: https://jekyllrb.com/docs/front-matter/ "Jekyll docs about front matter" +[Jekyll docs page about layouts]: https://jekyllrb.com/docs/layouts/ "Jekyll docs about layouts" +[Jekyll's front matter defaults]: https://jekyllrb.com/docs/configuration/front-matter-defaults/ "Jekyll docs about front matter defaults" +[Jekyll docs about inheritance]: https://jekyllrb.com/docs/layouts/#inheritance "Jekyll docs about inheritance" diff --git a/docs/layout/minimal/default-child.md b/docs/layout/minimal/default-child.md new file mode 100644 index 0000000..3035580 --- /dev/null +++ b/docs/layout/minimal/default-child.md @@ -0,0 +1,8 @@ +--- +title: Default layout child page +layout: default +parent: A minimal layout page +grand_parent: Layout +--- + +This is a child page that uses the same minimal layout as its parent page. diff --git a/docs/layout/minimal/minimal-child.md b/docs/layout/minimal/minimal-child.md new file mode 100644 index 0000000..26bcff7 --- /dev/null +++ b/docs/layout/minimal/minimal-child.md @@ -0,0 +1,8 @@ +--- +title: Minimal layout child page +layout: minimal +parent: A minimal layout page +grand_parent: Layout +--- + +This is a child page that uses the same minimal layout as its parent page. diff --git a/docs/layout/minimal/minimal.md b/docs/layout/minimal/minimal.md new file mode 100644 index 0000000..1821d67 --- /dev/null +++ b/docs/layout/minimal/minimal.md @@ -0,0 +1,12 @@ +--- +title: A minimal layout page +layout: minimal +parent: Layout +has_children: true +--- + +# A minimal layout page + +This page illustrates the built-in layout `minimal`. + +One of its child pages also uses the minimal layout; the other child pages uses the default layout.