mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
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:
3
website/.gitignore
vendored
3
website/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules/
|
||||
public/
|
||||
static/
|
||||
static/*
|
||||
!static/js/
|
||||
content/learn/node-catalog
|
||||
|
||||
@@ -4,7 +4,7 @@ template = "section.html"
|
||||
|
||||
[extra]
|
||||
css = ["/page/index.css", "/component/carousel.css", "/component/feature-icons.css", "/component/feature-box.css", "/component/youtube-embed.css"]
|
||||
js = ["/js/carousel.js", "/js/youtube-embed.js", "/js/video-autoplay.js"]
|
||||
js = ["/js/component/carousel.js", "/js/component/youtube-embed.js", "/js/component/video-autoplay.js"]
|
||||
linked_js = []
|
||||
meta_description = "Open source free software. A vector graphics creativity suite with a clean, intuitive interface. Opens instantly (no signup) and runs locally in a browser. Exports SVG, PNG, JPG."
|
||||
+++
|
||||
|
||||
@@ -10,7 +10,7 @@ summary = "Looking back on 2023, we reflect on our significant achievements and
|
||||
reddit = "https://www.reddit.com/r/graphite/comments/18xmoti/blog_post_looking_back_on_2023_and_whats_next/"
|
||||
twitter = "https://twitter.com/GraphiteEditor/status/1742576805532577937"
|
||||
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ reddit = "https://www.reddit.com/r/graphite/comments/1i3umnl/blog_post_year_in_r
|
||||
twitter = "https://x.com/GraphiteEditor/status/1880404337345851612"
|
||||
bluesky = "https://bsky.app/profile/graphiteeditor.bsky.social/post/3lfxysayh622g"
|
||||
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ reddit = "https://www.reddit.com/r/graphite/comments/1jplm6t/internships_for_a_r
|
||||
twitter = "https://x.com/GraphiteEditor/status/1907384498389651663"
|
||||
bluesky = "https://bsky.app/profile/graphiteeditor.bsky.social/post/3llt7lbmm4s24"
|
||||
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -220,13 +220,3 @@ Also available to individuals wanting to make a larger impact. [Reach out](/cont
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- <div class="fundraising loading" data-fundraising>
|
||||
<div class="fundraising-bar" data-fundraising-bar style="--fundraising-percent: 0%">
|
||||
<div class="fundraising-bar-progress"></div>
|
||||
</div>
|
||||
<div class="goal-metrics">
|
||||
<span data-fundraising-percent>Progress: <span data-dynamic>0</span>%</span>
|
||||
<span data-fundraising-goal>Goal: $<span data-dynamic>0</span>/month</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
@@ -3,7 +3,7 @@ title = "Graphite features"
|
||||
|
||||
[extra]
|
||||
css = ["/page/features.css", "/component/feature-box.css", "/component/feature-icons.css", "/component/youtube-embed.css"]
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
+++
|
||||
|
||||
<section>
|
||||
|
||||
@@ -5,7 +5,7 @@ page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
book = true
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -54,16 +54,16 @@ The right side of the control bar has controls related to the active document an
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Overlays | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |
|
||||
| Snapping | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu. Each option has a tooltip explaining what it does by hovering the cursor over it.</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/snapping-popover__5.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Align with Edges**: Snaps to horizontal/vertical alignment with the edges of any layer's bounding box.</li><li>**Corner Points**: Snaps to the four corners of any layer's bounding box.</li><li>**Center Points**: Snaps to the center point of any layer's bounding box.</li><li>**Edge Midpoints**: Snaps to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Distribute Evenly**: Snaps to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Corner Points** and **Center Points** must be enabled).</li></ul></p><p>Snapping options relating to **Paths**:</p><p><ul><li>**Align with Anchor Points**: Snaps to horizontal/vertical alignment with the anchor points of any vector path.</li><li>**Anchor Points**: Snaps to the anchor point of any vector path.</li><li>**Line Midpoints**: Snaps to the point at the middle of any straight line segment of a vector path.</li><li>**Path Intersection Points**: Snaps to any points where vector paths intersect.</li><li>**Along Paths**: Snaps along the length of any vector path.</li><li>**Normal to Paths**: Snaps a line to a point perpendicular to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li><li>**Tangent to Paths**: Snaps a line to a point tangent to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li></ul></p> |
|
||||
| Grid | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/grid-rectangular-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/grid-isometric-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |
|
||||
| Render Mode | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixel Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p><p>**SVG Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as an SVG image.</p> |
|
||||
| Zoom In | <p>Zooms the viewport in to the next whole increment.</p> |
|
||||
| Zoom Out | <p>Zooms the viewport out to the next whole increment.</p> |
|
||||
| Reset Tilt and Zoom to 100% | <p>Resets the viewport tilt to 0°. Resets the viewport zoom to 100% which matches the canvas and viewport pixel scale 1:1.</p> |
|
||||
| Viewport Zoom | <p>Indicates the current zoom level of the viewport and allows precise values to be chosen.</p> |
|
||||
| Viewport Tilt | <p>Hidden except when the viewport is tilted (use the *View* > *Tilt* menu action). Indicates the current tilt angle of the viewport and allows precise values to be chosen.</p> |
|
||||
| Node Graph | <p>Toggles the visibility of the overlaid node graph.</p> |
|
||||
| **Overlays** | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |
|
||||
| **Snapping** | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu. Each option has a tooltip explaining what it does by hovering the cursor over it.</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/snapping-popover__5.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Align with Edges**: Snaps to horizontal/vertical alignment with the edges of any layer's bounding box.</li><li>**Corner Points**: Snaps to the four corners of any layer's bounding box.</li><li>**Center Points**: Snaps to the center point of any layer's bounding box.</li><li>**Edge Midpoints**: Snaps to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Distribute Evenly**: Snaps to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Corner Points** and **Center Points** must be enabled).</li></ul></p><p>Snapping options relating to **Paths**:</p><p><ul><li>**Align with Anchor Points**: Snaps to horizontal/vertical alignment with the anchor points of any vector path.</li><li>**Anchor Points**: Snaps to the anchor point of any vector path.</li><li>**Line Midpoints**: Snaps to the point at the middle of any straight line segment of a vector path.</li><li>**Path Intersection Points**: Snaps to any points where vector paths intersect.</li><li>**Along Paths**: Snaps along the length of any vector path.</li><li>**Normal to Paths**: Snaps a line to a point perpendicular to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li><li>**Tangent to Paths**: Snaps a line to a point tangent to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li></ul></p> |
|
||||
| **Grid** | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/grid-rectangular-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src="https://static.graphite.art/content/learn/interface/document-panel/grid-isometric-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |
|
||||
| **Render Mode** | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixel Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p><p>**SVG Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as an SVG image.</p> |
|
||||
| **Zoom In** | <p>Zooms the viewport in to the next whole increment.</p> |
|
||||
| **Zoom Out** | <p>Zooms the viewport out to the next whole increment.</p> |
|
||||
| **Reset Tilt and Zoom to 100%** | <p>Resets the viewport tilt to 0°. Resets the viewport zoom to 100% which matches the canvas and viewport pixel scale 1:1.</p> |
|
||||
| **Viewport Zoom** | <p>Indicates the current zoom level of the viewport and allows precise values to be chosen.</p> |
|
||||
| **Viewport Tilt** | <p>Hidden except when the viewport is tilted (use the *View* > *Tilt* menu action). Indicates the current tilt angle of the viewport and allows precise values to be chosen.</p> |
|
||||
| **Node Graph** | <p>Toggles the visibility of the overlaid node graph.</p> |
|
||||
|
||||
## Tool shelf
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 1
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 2 # Chapter number
|
||||
js = ["/js/youtube-embed.js"]
|
||||
js = ["/js/component/youtube-embed.js"]
|
||||
css = ["/component/youtube-embed.css"]
|
||||
+++
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ title = "Editor structure"
|
||||
|
||||
[extra]
|
||||
order = 1 # Page number after chapter intro
|
||||
css = ["/page/developer-guide-editor-structure.css"]
|
||||
js = ["/js/developer-guide-editor-structure.js"]
|
||||
css = ["/page/contributor-guide/editor-structure.css"]
|
||||
js = ["/js/page/contributor-guide/editor-structure.js"]
|
||||
+++
|
||||
|
||||
The Graphite editor is the application users interact with to create documents. Its code is a single Rust crate that lives below the frontend (web code) and above [Graphene](../../graphene) (the node-based graphics engine). The main business logic of all visual editing is handled by the editor backend. When running in the browser, it is compiled to WebAssembly and passes messages to the frontend.
|
||||
|
||||
@@ -32,7 +32,6 @@ Clone the project to a convenient location:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/GraphiteEditor/Graphite.git
|
||||
cd Graphite
|
||||
```
|
||||
|
||||
## Development builds
|
||||
@@ -43,20 +42,27 @@ From either the `/` (root) or `/frontend` directories, you can run the project b
|
||||
npm start
|
||||
```
|
||||
|
||||
This spins up the dev server at <http://localhost:8080> with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and shut it down by double pressing <kbd>Ctrl</kbd><kbd>C</kbd>. You sometimes may need to reload the browser's page if hot reloading didn't behave right— always refresh when Rust recompiles.
|
||||
This spins up the dev server at <http://localhost:8080> with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and shut it down by double pressing <kbd>Ctrl</kbd><kbd>C</kbd>. TypeScript and HTML changes require a manual page reload to fix broken state.
|
||||
|
||||
This method compiles Graphite code in debug mode which includes debug symbols for viewing function names in stack traces. But be aware, it runs slower and the Wasm binary is much larger. (Having your browser's developer tools open will also significantly impact performance in both debug and release builds, so it's best to close that when not in use.)
|
||||
|
||||
To run the dev server in optimized mode, which is faster and produces a smaller Wasm binary:
|
||||
<details>
|
||||
<summary>Dev server optimized build instructions: click here</summary>
|
||||
|
||||
On rare occasions (like while running advanced performance profiles or proxying the dev server connection over a slow network where the >100 MB unoptimized binary size would pose an issue), you may need to run the dev server with release optimizations. To do that while keeping debug symbols:
|
||||
|
||||
```sh
|
||||
# Includes debug symbols
|
||||
npm run profiling
|
||||
```
|
||||
|
||||
# Excludes (most) debug symbols, used in release builds
|
||||
To run the dev server without debug symbols, using the same release optimizations as production builds:
|
||||
|
||||
```sh
|
||||
npm run production
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Production build instructions: click here</summary>
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ body > .page {
|
||||
// ELEMENT SPACING RULES
|
||||
// =====================
|
||||
|
||||
:is(h1, h2, h3, h4, article > :first-child, details > summary) ~ :is(p, ul, ol, ol li p, img, a:has(> img:only-child)),
|
||||
:is(h1, h2, h3, h4, article > :first-child, details > summary) ~ :is(p, ul, ol, ol li p, img, details, a:has(> img:only-child)),
|
||||
:is(h1, h2, h3, h4, article > :first-child) ~ :is(ul, ol) li p + img,
|
||||
:is(h1, h2, h3, h4, p) ~ .feature-icons,
|
||||
p ~ :is(h1, h2, h3, h4, details summary, blockquote, .image-comparison, .video-background, .youtube-embed),
|
||||
@@ -429,12 +429,12 @@ h6 {
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1rem * 16 / 9);
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1rem * 4 / 3);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h4,
|
||||
@@ -496,6 +496,10 @@ table {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
.reading-material.reading-material {
|
||||
font-size: 16px;
|
||||
line-height: 1.625;
|
||||
max-width: var(--max-width-reading-material);
|
||||
|
||||
article {
|
||||
width: 100%;
|
||||
|
||||
h2 {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
@@ -23,6 +13,7 @@
|
||||
h5,
|
||||
h6 {
|
||||
display: block;
|
||||
margin-top: 80px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.structure-outline {
|
||||
font-family: monospace;
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
margin-top: 20px;
|
||||
|
||||
@@ -107,46 +107,3 @@
|
||||
background-color: var(--color-ale);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
// .fundraising {
|
||||
// margin-top: 20px;
|
||||
// width: 100%;
|
||||
//
|
||||
// .fundraising-bar {
|
||||
// width: 100%;
|
||||
// height: 32px;
|
||||
// border-radius: 10000px;
|
||||
// background: var(--color-fog);
|
||||
// overflow: hidden;
|
||||
//
|
||||
// .fundraising-bar-progress {
|
||||
// width: calc(var(--fundraising-percent) - (4px * 2) - (32px - 4px * 2));
|
||||
// padding-left: calc(32px - 4px * 2);
|
||||
// height: calc(100% - 4px * 2);
|
||||
// margin: 4px;
|
||||
// border-radius: 10000px;
|
||||
// background: linear-gradient(to right, var(--color-navy), var(--color-crimson));
|
||||
// transition: opacity 1s, width 2s;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .goal-metrics {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// font-weight: 800;
|
||||
// margin-top: 8px;
|
||||
// margin-left: 20px;
|
||||
// width: calc(100% - 40px);
|
||||
|
||||
// > span {
|
||||
// transition: opacity 1s;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// &.fundraising.loading {
|
||||
// .goal-metrics > span,
|
||||
// .fundraising-bar .fundraising-bar-progress {
|
||||
// opacity: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
3
website/sass/page/user-manual/node-catalog.scss
Normal file
3
website/sass/page/user-manual/node-catalog.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
table tr td:first-child a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
14
website/sass/page/user-manual/node-category.scss
Normal file
14
website/sass/page/user-manual/node-category.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
#nodes + table tr {
|
||||
th:last-child {
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
width: 0;
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
website/sass/page/user-manual/node.scss
Normal file
14
website/sass/page/user-manual/node.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
:is(#context, #inputs, #outputs) + table tr {
|
||||
th:is(:first-child, :nth-child(3)) {
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:is(:first-child, :nth-child(3)) {
|
||||
width: 0;
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,12 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
|
||||
svg {
|
||||
fill: var(--color-navy);
|
||||
flex: 0 0 auto;
|
||||
@@ -77,7 +77,7 @@
|
||||
// Overlaid fold-out menu for chapter selection
|
||||
@media screen and (max-width: 1000px) {
|
||||
gap: 0;
|
||||
|
||||
|
||||
.chapters {
|
||||
position: sticky;
|
||||
width: 0;
|
||||
@@ -95,7 +95,7 @@
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.wrapper-outer {
|
||||
position: absolute;
|
||||
background: white;
|
||||
@@ -110,8 +110,8 @@
|
||||
border-right: var(--border-thickness) solid var(--color-walnut);
|
||||
box-sizing: border-box;
|
||||
transition: left 0.25s ease-in-out;
|
||||
left: calc(-1 * (var(--aside-width) + 10px));
|
||||
width: var(--aside-width);
|
||||
left: calc(-1 * (Min(var(--aside-width), 100vw) + 10px));
|
||||
width: Min(var(--aside-width), 100vw);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
@@ -128,8 +128,9 @@
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding-right: var(--page-edge-padding);
|
||||
margin-left: -24px;
|
||||
|
||||
ul:first-of-type {
|
||||
> ul:first-of-type {
|
||||
margin-top: calc(120 * var(--variable-px) + var(--align-with-article-title-letter-cap-heights));
|
||||
}
|
||||
|
||||
@@ -173,68 +174,107 @@
|
||||
align-self: flex-start;
|
||||
overflow-y: auto;
|
||||
top: 0;
|
||||
width: var(--aside-width);
|
||||
width: Min(var(--aside-width), 100vw);
|
||||
max-height: 100vh;
|
||||
margin-top: -40px;
|
||||
margin-top: calc(-40 * var(--variable-px));
|
||||
flex: 0 1 auto;
|
||||
--level-indent: 0.75rem;
|
||||
|
||||
&.contents > ul,
|
||||
.wrapper-inner > ul {
|
||||
&:first-of-type {
|
||||
margin-top: calc(40px + var(--align-with-article-title-letter-cap-heights));
|
||||
margin-top: calc(40 * var(--variable-px) + var(--align-with-article-title-letter-cap-heights));
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: calc(40 * var(--variable-px));
|
||||
}
|
||||
|
||||
> ul {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.contents > ul > ul :is(ul, li),
|
||||
.wrapper-inner > ul > ul > ul,
|
||||
.wrapper-inner > ul > ul > ul :is(ul, li) {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 40px;
|
||||
|
||||
&,
|
||||
ul,
|
||||
li {
|
||||
margin-top: calc(40 * var(--variable-px));
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-left: 1em;
|
||||
margin-left: var(--level-indent);
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 2em;
|
||||
|
||||
ul {
|
||||
margin-left: 3em;
|
||||
|
||||
ul {
|
||||
margin-left: 4em;
|
||||
|
||||
ul {
|
||||
margin-left: 5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
li:has(> label > input:not(:checked)) + ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 0.5em;
|
||||
|
||||
a {
|
||||
color: var(--color-walnut);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-crimson);
|
||||
}
|
||||
}
|
||||
|
||||
font-size: 0;
|
||||
|
||||
&:not(.title) a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
&.title,
|
||||
&.chapter {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
vertical-align: bottom;
|
||||
margin-right: 4px;
|
||||
width: 20px;
|
||||
height: calc(1rem * 1.5);
|
||||
|
||||
&:has(input):hover {
|
||||
background: var(--color-fog);
|
||||
}
|
||||
|
||||
&:has(input)::before {
|
||||
content: "";
|
||||
background: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><polygon fill="%2316323f" points="4,0 1,0 6,5 1,10 4,10 9,5 4,0" /></svg>\
|
||||
');
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
inset: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
&:has(input:checked)::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-walnut);
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-crimson);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,9 +285,7 @@
|
||||
}
|
||||
|
||||
ul a {
|
||||
display: block;
|
||||
padding-left: 1em;
|
||||
text-indent: -1em;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
window.addEventListener("DOMContentLoaded", initializeFundraisingBar);
|
||||
|
||||
function initializeFundraisingBar() {
|
||||
const VISIBILITY_COVERAGE_FRACTION = 0.5;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
const fundraising = document.querySelector("[data-fundraising]");
|
||||
if (!fundraising) return;
|
||||
const bar = fundraising.querySelector("[data-fundraising-bar]");
|
||||
const dynamicPercent = fundraising.querySelector("[data-fundraising-percent] [data-dynamic]");
|
||||
const dynamicGoal = fundraising.querySelector("[data-fundraising-goal] [data-dynamic]");
|
||||
if (!(fundraising instanceof HTMLElement && bar instanceof HTMLElement && dynamicPercent instanceof HTMLElement && dynamicGoal instanceof HTMLElement)) return;
|
||||
|
||||
const setFundraisingGoal = async () => {
|
||||
const request = await fetch("https://graphite.art/fundraising-goal");
|
||||
/** @type {{ percentComplete: number, targetValue: number }} */
|
||||
const data = await request.json();
|
||||
|
||||
fundraising.classList.remove("loading");
|
||||
bar.style.setProperty("--fundraising-percent", `${data.percentComplete}%`);
|
||||
dynamicPercent.textContent = `${data.percentComplete}`;
|
||||
dynamicGoal.textContent = `${data.targetValue}`;
|
||||
|
||||
loaded = true;
|
||||
};
|
||||
new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) setFundraisingGoal();
|
||||
});
|
||||
},
|
||||
{ threshold: VISIBILITY_COVERAGE_FRACTION },
|
||||
).observe(fundraising);
|
||||
}
|
||||
@@ -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 © {{ 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 © {{ now() | date(format = "%Y") }} Graphite Labs, LLC (an open source community organization)</span>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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 }}">» {{ 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 }}">» {{ 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>
|
||||
|
||||
87
website/templates/macros/book-outline.html
Normal file
87
website/templates/macros/book-outline.html
Normal file
@@ -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 -%}
|
||||
Reference in New Issue
Block a user