Add the auto-generated node catalog to the website's user manual (#3662)

* Generate the MVP node catalog in the manual (with some placeholders)

* Implement nearly the rest of everything

* Move to the tools directory and make it generate nicer default values

* Add category descriptions

* Organize file structure and improve type naming

* Improve book table of contents code

* Add collapsing chapter navigation to the book template

* Add to build workflow

* Clean up site structure
This commit is contained in:
Keavon Chambers
2026-01-20 22:52:03 -08:00
committed by GitHub
parent 5543afd44b
commit 7af60e02a3
61 changed files with 1131 additions and 384 deletions
+55 -47
View File
@@ -29,7 +29,6 @@
{%- endblock %}
{#- ======================================================================== -#}
{#- ON EVERY PAGE OF THE SITE: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}
{#- ======================================================================== -#}
{%- set global_linked_js = [] -%}
@@ -41,6 +40,7 @@
{{ throw(message = "------------------------------------------------------------> FONTS ARE NOT INSTALLED! Before running Zola, execute `npm install` from the `/website` directory.") }}
{%- endif -%}
{#- ================================================================================ -#}
{#- RETRIEVE FROM TEMPLATES AND PAGES: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}
{#- ================================================================================ -#}
{%- set linked_css = page.extra.linked_css | default(value = []) | concat(with = linked_css | default(value = [])) -%}
@@ -48,6 +48,7 @@
{%- set css = page.extra.css | default(value = []) | concat(with = css | default(value = [])) -%}
{%- set js = page.extra.js | default(value = []) | concat(with = js | default(value = [])) -%}
{#- =================================================== -#}
{#- COMBINE THE GLOBAL AND TEMPLATE/PAGE RESOURCE LISTS -#}
{#- =================================================== -#}
{%- set linked_css_list = linked_css | concat(with = global_linked_css) -%}
@@ -55,6 +56,7 @@
{%- set css_list = css | concat(with = global_css) -%}
{%- set js_list = js | concat(with = global_js) -%}
{#- ================================================================================== -#}
{#- CONDITIONALLY MAKE ONLY PROD BUILDS ACTUALLY INLINE THE CSS AND JS FOR CLEANLINESS -#}
{#- ================================================================================== -#}
{%- if get_env(name = "MODE", default = "dev") != "prod" -%}
@@ -64,18 +66,21 @@
{%- set js_list = [] -%}
{%- endif -%}
{#- ================ -#}
{#- INSERT CSS LINKS -#}
{#- ================ -#}
{%- for path in linked_css_list %}
<link rel="stylesheet" href="{{ path | safe }}" />
{%- endfor %}
{#- =============== -#}
{#- INSERT JS LINKS -#}
{#- =============== -#}
{%- for path in linked_js_list %}
<script src="{{ path | safe }}"></script>
{%- endfor %}
{#- ====================== -#}
{#- INSERT INLINE CSS CODE -#}
{#- ====================== -#}
{%- if css_list | length > 0 %}
@@ -86,6 +91,7 @@
{{ "</" ~ "style>" | safe }}
{%- endif %}
{#- ===================== -#}
{#- INSERT INLINE JS CODE -#}
{#- ===================== -#}
{%- for path in js_list %}
@@ -97,51 +103,53 @@
{{- get_env(name = "INDEX_HTML_HEAD_INCLUSION", default = "") | safe }}
</head>
<body>
<div class="page">
<header>
<nav>
<div class="row">
<div class="left">
<a href="/">
<img src="https://static.graphite.art/logos/graphite-logo-solid.svg" alt="Graphite Logo" />
</a>
</div>
<div class="right">
<a href="/learn">Learn</a>
<a href="/features">Features</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
<a href="/volunteer">Volunteer</a>
<a href="/donate" class="heart">Donate</a>
<a href="https://editor.graphite.art" class="button arrow">Launch</a>
</div>
</div>
</nav>
<svg class="ripple" xmlns="http://www.w3.org/2000/svg">
<path d="M 0,15 l 10000,0" />
</svg>
<hr />
</header>
<main>
{%- filter replace(from = "<!-- replacements::blog_posts(count = 2) -->", to = replacements::blog_posts(count = 2)) -%}
{%- filter replace(from = "<!-- replacements::text_balancer() -->", to = replacements::text_balancer()) -%}
{%- filter replace(from = "<!-- replacements::hierarchical_message_system_tree() -->", to = replacements::hierarchical_message_system_tree()) -%}
{%- block content -%}{%- endblock -%}
{%- endfilter -%}
{%- endfilter -%}
{%- endfilter -%}
</main>
<footer>
<hr />
<nav>
<a href="https://github.com/GraphiteEditor/Graphite" class="link not-uppercase">GitHub</a>
<a href="/license" class="link not-uppercase">License</a>
<a href="/logo" class="link not-uppercase">Logo</a>
<a href="/press" class="link not-uppercase">Press</a>
<a href="/contact" class="link not-uppercase">Contact</a>
</nav>
<span>Copyright &copy; {{ now() | date(format = "%Y") }} Graphite Labs, LLC (an open source community organization)</span>
</footer>
</div>
<div class="page">
<header>
<nav>
<div class="row">
<div class="left">
<a href="/">
<img src="https://static.graphite.art/logos/graphite-logo-solid.svg" alt="Graphite Logo" />
</a>
</div>
<div class="right">
<a href="/learn">Learn</a>
<a href="/features">Features</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
<a href="/volunteer">Volunteer</a>
<a href="/donate" class="heart">Donate</a>
<a href="https://editor.graphite.art" class="button arrow">Launch</a>
</div>
</div>
</nav>
<svg class="ripple" xmlns="http://www.w3.org/2000/svg">
<path d="M 0,15 l 10000,0" />
</svg>
<hr />
</header>
<main>
{# This is a comment. It exists to prevent the {%- -%} on the lines below from removing the line break between `<main>` and the `content` block #}
{%- filter replace(from = "<!-- replacements::blog_posts(count = 2) -->", to = replacements::blog_posts(count = 2)) -%}
{%- filter replace(from = "<!-- replacements::text_balancer() -->", to = replacements::text_balancer()) -%}
{%- filter replace(from = "<!-- replacements::hierarchical_message_system_tree() -->", to = replacements::hierarchical_message_system_tree()) -%}
{%- block content -%}{%- endblock -%}
{%- endfilter -%}
{%- endfilter -%}
{%- endfilter -%}
{# This is a comment. It exists to prevent the {%- -%} on the lines above from removing the line break between the `content` block and `</main>` #}
</main>
<footer>
<hr />
<nav>
<a href="https://github.com/GraphiteEditor/Graphite" class="link not-uppercase">GitHub</a>
<a href="/license" class="link not-uppercase">License</a>
<a href="/logo" class="link not-uppercase">Logo</a>
<a href="/press" class="link not-uppercase">Press</a>
<a href="/contact" class="link not-uppercase">Contact</a>
</nav>
<span>Copyright &copy; {{ now() | date(format = "%Y") }} Graphite Labs, LLC (an open source community organization)</span>
</footer>
</div>
</body>
</html>
+56 -92
View File
@@ -1,38 +1,52 @@
{% extends "base.html" %}
{% import "macros/book-outline.html" as book_outline %}
{%- block head -%}{%- set page = page | default(value = section) -%}
{%- set title = page.title -%}
{%- set meta_article_type = true -%}
{%- set meta_description = page.extra.summary | default(value = page.content | striptags | safe | linebreaksbr | replace(from = "<br>", to = " ") | replace(from = " ", to = " ") | trim | truncate(length = 200)) -%}
{%- set css = ["/template/book.css", "/layout/reading-material.css", "/component/code-snippet.css"] -%}
{%- set js = ["/js/book.js"] -%}
{%- set js = ["/js/template/book.js"] -%}
{%- endblock head -%}
{%- block content -%}{%- set page = page | default(value = section) -%}
{# Search this page-or-section's ancestor tree for a section that identifies itself as a book, and save it to a `book` variable #}
{% for ancestor_path in page.ancestors | concat(with = page.relative_path) %}
{# Get the ancestor section from this ancestor path string #}
{% if ancestor_path is ending_with("/_index.md") %}
{% set potential_book = get_section(path = ancestor_path) %}
{% endif %}
{#- Search this page-or-section's ancestor tree for a section that identifies itself as a book, and save it to a `book` variable -#}
{%- for ancestor_path in page.ancestors | concat(with = page.relative_path) -%}
{#- Get the ancestor section from this ancestor path string -#}
{%- if ancestor_path is ending_with("/_index.md") -%}
{%- set potential_book = get_section(path = ancestor_path) -%}
{%- endif -%}
{# Check if the ancestor section is the root of a book, and if so, set it to a variable accessible outside the loop #}
{% if potential_book.extra.book %}
{% set_global book = get_section(path = potential_book.path ~ "_index.md" | trim_start_matches(pat="/")) %}
{% endif %}
{% endfor %}
{#- Check if the ancestor section is the root of a book, and if so, set it to a variable accessible outside the loop -#}
{%- if potential_book.extra.book -%}
{%- set_global book = get_section(path = potential_book.path ~ "_index.md" | trim_start_matches(pat = "/")) -%}
{%- endif -%}
{%- endfor -%}
{# Map this book's chapter path strings to an array of sections #}
{% set chapters = [] %}
{% for chapter_path in book.subsections %}
{% set_global chapters = chapters | concat(with = get_section(path = chapter_path)) %}
{% endfor %}
{% set chapters = chapters | sort(attribute = "extra.order") %}
{#- Map this book's chapter path strings to an array of sections -#}
{%- set chapters = [] -%}
{%- for chapter_path in book.subsections -%}
{%- set_global chapters = chapters | concat(with = get_section(path = chapter_path)) -%}
{%- endfor -%}
{%- set chapters = chapters | sort(attribute = "extra.order") -%}
{# A flat list of all pages in the ToC, initialized to just the book root section but updated when we generate the ToC #}
{% set flat_pages = [book] %}
{% set flat_index_of_this = 0 %}
{#- A flat list of all pages in the ToC -#}
{%- set flattened_outline = book_outline::flatten_book_outline(section = book) -%}
{%- set flat_pages_list = book.path ~ ",,,,," ~ book.title ~ ";;;;;" ~ flattened_outline | split(pat = ";;;;;") -%}
{%- set flat_index_of_this = 0 -%}
{%- set flat_pages_path = [] -%}
{%- set flat_pages_title = [] -%}
{%- for item_str in flat_pages_list -%}
{%- if item_str | trim | length > 0 -%}
{%- set parts = item_str | split(pat = ",,,,,") -%}
{%- if current_path == parts | first -%}
{%- set_global flat_index_of_this = loop.index0 -%}
{%- endif -%}
{%- set_global flat_pages_path = flat_pages_path | concat(with = parts | first) -%}
{%- set_global flat_pages_title = flat_pages_title | concat(with = parts | last) -%}
{%- endif -%}
{%- endfor -%}
<section class="three-column-layout">
<aside class="chapters" data-chapters>
@@ -43,30 +57,7 @@
<polygon points="20.7,4.7 19.3,3.3 12,10.6 4.7,3.3 3.3,4.7 10.6,12 3.3,19.3 4.7,20.7 12,13.4 19.3,20.7 20.7,19.3 13.4,12" />
</svg>
</button>
<ul>
<li class="title{% if current_path == book.path %} active{% endif %}"><a href="{{ book.path | safe }}" title="{{ book.title | safe }}">{{ book.title }}</a></li>
</ul>
{% for chapter in chapters %}
<ul>
<li class="chapter{% if current_path == chapter.path %} active{% endif %}"><a href="{{ chapter.path | safe }}" title="{{ chapter.title | safe }}">&raquo; {{ chapter.title }}</a></li>
{% set_global flat_pages = flat_pages | concat(with = chapter) %}
{% if chapter == page %}{% set_global flat_index_of_this = flat_pages | length - 1 %}{% endif %}
{% if chapter.pages %}
{% for chapter_page in chapter.pages | sort(attribute = "extra.order") %}
{% set_global flat_pages = flat_pages | concat(with = chapter_page) %}
{% if chapter_page == page %}{% set_global flat_index_of_this = flat_pages | length - 1 %}{% endif %}
<li {% if current_path == chapter_page.path %}class="active"{% endif %}><a href="{{ chapter_page.path | safe }}" title="{{ page.title | safe }}">&raquo; {{ chapter_page.title }}</a></li>
{% endfor %}
{% endif %}
</ul>
{% endfor %}
{{- book_outline::render_book_outline(parent = book, current_path = current_path, index = 0, indents = 3) }}
</div>
</div>
</aside>
@@ -86,39 +77,41 @@
</h1>
</div>
<article>
{{ page.content | safe }}
{{ page.content | safe }}
</article>
<hr />
<div class="prev-next">
{% if flat_index_of_this >= 1 %}
{% set prev = flat_pages | nth(n = flat_index_of_this - 1) %}
{% endif %}
{% if prev %}
<a href="{{ prev.path | safe }}" title="{{ prev.title | safe }}">
{%- if flat_index_of_this >= 1 -%}
{%- set prev_path = flat_pages_path | nth(n = flat_index_of_this - 1) -%}
{%- set prev_title = flat_pages_title | nth(n = flat_index_of_this - 1) -%}
{%- endif -%}
{%- if prev_path %}
<a href="{{ prev_path | safe }}" title="{{ prev_title | safe }}">
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
<polygon points="24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20" />
</svg>
{{ prev.title }}
{{ prev_title }}
</a>
{% else %}
<a><!-- Spacer --></a>
{% endif %}
{%- else -%}
<a>{#- Spacer -#}</a>
{%- endif -%}
{% if flat_index_of_this < flat_pages | length - 1 %}
{% set next = flat_pages | nth(n = flat_index_of_this + 1) %}
{% endif %}
{% if next %}
<a href="{{ next.path | safe }}" title="{{ next.title | safe }}">
{{ next.title }}
{%- if flat_index_of_this < flat_pages_path | length - 1 -%}
{%- set next_path = flat_pages_path | nth(n = flat_index_of_this + 1) -%}
{%- set next_title = flat_pages_title | nth(n = flat_index_of_this + 1) -%}
{%- endif -%}
{%- if next_path %}
<a href="{{ next_path | safe }}" title="{{ next_title | safe }}">
{{ next_title }}
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
<polygon points="16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20" />
</svg>
</a>
{% endif %}
{%- endif %}
</div>
</div>
</section>
@@ -130,36 +123,7 @@
{% if page.toc | length > 0 %}Contents<span> (top ↑)</span>{% else %}Back to top ↑{% endif %}
</a>
</li>
</ul>
<ul>
{% for depth_1 in page.toc %}
<li><a href="#{{ depth_1.id }}" title="{{ depth_1.title | safe }}">{{ depth_1.title }}</a></li>
{% for depth_2 in depth_1.children %}
<ul>
<li><a href="#{{ depth_2.id }}" title="{{ depth_2.title | safe }}">{{ depth_2.title }}</a></li>
{% for depth_3 in depth_2.children %}
<ul>
<li><a href="#{{ depth_3.id }}" title="{{ depth_3.title | safe }}">{{ depth_3.title }}</a></li>
{% for depth_4 in depth_3.children %}
<ul>
<li><a href="#{{ depth_4.id }}" title="{{ depth_4.title | safe }}">{{ depth_4.title }}</a></li>
{% for depth_5 in depth_4.children %}
<ul>
<li><a href="#{{ depth_5.id }}" title="{{ depth_5.title | safe }}">{{ depth_5.title }}</a></li>
{% for depth_6 in depth_5.children %}
<ul>
<li><a href="#{{ depth_6.id }}" title="{{ depth_6.title | safe }}">{{ depth_6.title }}</a></li>
</ul>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
{{- book_outline::render_book_page_toc(children = page.toc, indents = 1) }}
</ul>
</aside>
</section>
@@ -0,0 +1,87 @@
{# Recursively render a page's headings table of contents #}
{%- macro render_book_page_toc(children, indents) -%}
{%- set tabs = "" -%}
{%- for i in range(end = indents) -%}
{%- set_global tabs = tabs ~ " " -%}
{%- endfor -%}
{%- if children | length > 0 %}
{{ tabs }}<ul>
{%- for child in children %}
{{ tabs }} <li><a href="#{{ child.id }}" title="{{ child.title | safe }}">{{ child.title }}</a></li>
{{- self::render_book_page_toc(children = child.children, indents = indents + 1) -}}
{%- endfor %}
{{ tabs }}</ul>
{%- endif -%}
{%- endmacro render_book_page_toc -%}
{# Recursively render a book's chapters table of contents #}
{%- macro render_book_outline(parent, current_path, index, indents) -%}
{#- Setup -#}
{%- set chapters = parent.pages | default(value = []) -%}
{%- if index == 0 -%}
{%- set_global chapters = [parent] -%}
{%- else -%}
{%- for subsection_path in parent.subsections -%}
{%- set_global chapters = chapters | concat(with = get_section(path = subsection_path)) -%}
{%- endfor -%}
{%- endif -%}
{%- if index > 0 -%}
{%- set_global chapters = chapters | sort(attribute = "extra.order") -%}
{%- endif -%}
{#- End of setup -#}
{%- set tabs = "" -%}
{%- for i in range(end = indents) -%}
{%- set_global tabs = tabs ~ " " -%}
{%- endfor -%}
{%- if chapters | length > 0 %}
{{ tabs }}<ul>
{%- for chapter in chapters %}
{%- set children = chapter.pages or chapter.subsections | default(value = []) -%}
{%- set_global classes = [] -%}
{%- if index == 0 -%}
{%- set_global classes = classes | concat(with = "title") -%}
{%- endif -%}
{%- if index == 1 -%}
{%- set_global classes = classes | concat(with = "chapter") -%}
{%- endif -%}
{%- if current_path == chapter.path -%}
{%- set_global classes = classes | concat(with = "active") -%}
{%- endif %}
{{ tabs }}<li {%- if classes | length > 0 %} class="{{ classes | join(sep = " ") }}"{% endif %}>
{{ tabs }}<label>{% if children and not index == 0 %}<input type="checkbox" {%- if current_path is starting_with(chapter.path) %} checked{% endif %} />{% endif %}</label>
{{ tabs }}<a href="{{ chapter.path | safe }}" title="{{ chapter.title | safe }}">{{ chapter.title }}</a>
{{ tabs }}</li>
{%- if children -%}
{{ self::render_book_outline(parent = chapter, current_path = current_path, index = index + 1, indents = indents + 1) }}
{%- endif %}
{%- endfor %}
{{ tabs }}</ul>
{%- endif -%}
{%- endmacro render_book_outline -%}
{# Recursively flatten the book outline to a string for sequential navigation #}
{%- macro flatten_book_outline(section) -%}
{#- Setup -#}
{%- set items = [] -%}
{%- if section.pages -%}
{%- set_global items = items | concat(with = section.pages) -%}
{%- endif -%}
{%- if section.subsections -%}
{%- for subsection_path in section.subsections -%}
{%- set subsection = get_section(path = subsection_path) -%}
{%- set_global items = items | concat(with = subsection) -%}
{%- endfor -%}
{%- endif -%}
{%- set items = items | sort(attribute = "extra.order") -%}
{#- End of setup -#}
{%- for item in items -%}
{{ item.path }},,,,,{{ item.title }};;;;;
{%- if item.pages or item.subsections -%}
{{ self::flatten_book_outline(section = item) }}
{%- endif -%}
{%- endfor -%}
{%- endmacro flatten_book_outline -%}