Skip to content

Commit 8c1e159

Browse files
authored
Merge pull request #1754 from gethinode/templatev2
Templatev2
2 parents 807fe9b + 03adb7e commit 8c1e159

File tree

195 files changed

+6306
-3240
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

195 files changed

+6306
-3240
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
_vendor/
22
prebuild/
33
prebuild-headers/
4+
prebuild-headers-dev/
5+
prebuild-headers-prod/
46
public/
57
resources/
68
node_modules/

CLAUDE.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,48 @@ Components are mounted to multiple locations via `hugo.toml`:
118118
- `data/structures/` - Schemas
119119
- `assets/scss/modules/bookshop/` - Styles
120120

121+
#### Bookshop Component Architecture
122+
123+
Bookshop components follow a two-layer architecture:
124+
125+
**1. Component partial** (e.g., `layouts/partials/assets/preview.html`):
126+
127+
- Contains the core component logic and rendering
128+
- Uses **component-specific arguments** (e.g., `url`, `device`, `heading` for preview)
129+
- These arguments should be defined in the component's structure file
130+
131+
**2. Bookshop wrapper** (e.g., `component-library/components/preview/preview.hugo.html`):
132+
133+
- Calls the component partial with component-specific arguments
134+
- Wraps output with `utilities/section.html` for section-level styling
135+
- Passes **section arguments** to the wrapper (e.g., `id`, `background`, `width`, `justify`, `wrapper`, `fluid`, `theme`, `cover`, `overlay-mode`, `section-class`, `bg-class`)
136+
137+
**Important distinctions:**
138+
139+
- **Section arguments are NOT part of the component partial** - they're handled by the section wrapper
140+
- **Structure files should only include component-specific arguments** - arguments actively used by the partial
141+
- **Section arguments are defined in Bookshop specs** (`.hugo.html` and `.bookshop.yml`) but not in the component's structure file
142+
- Example: `preview.yml` defines `url`, `device`, `heading` (used by `preview.html`), but NOT `background`, `width`, etc. (only used by section wrapper)
143+
144+
**Example structure:**
145+
146+
```hugo
147+
{{/* preview.hugo.html - Bookshop wrapper */}}
148+
{{ $raw := partial "assets/preview.html" (dict
149+
"url" .url {{/* component-specific */}}
150+
"device" .device {{/* component-specific */}}
151+
"heading" .heading {{/* component-specific */}}
152+
) }}
153+
154+
{{ partial "utilities/section.html" (dict
155+
"raw" $raw
156+
"background" .background {{/* section argument */}}
157+
"width" .width {{/* section argument */}}
158+
"theme" .theme {{/* section argument */}}
159+
{{/* ... other section arguments ... */}}
160+
)}}
161+
```
162+
121163
### Version 2 Architecture Philosophy
122164

123165
**Design Goal:** Hinode v2 is a minimal core theme that works standalone for documentation and blog sites. Optional features are provided through separate modules.
@@ -188,6 +230,164 @@ Extensive shortcode library in `layouts/_shortcodes/` provides Bootstrap compone
188230
- Content: image, video, table, timeline
189231
- Typography: abbr, kbd, mark, sub, sup
190232

233+
### Argument and Type Initialization System
234+
235+
Hinode uses `mod-utils` to provide a robust argument validation and initialization system
236+
for shortcodes, partials, and Bookshop components.
237+
238+
**Key components:**
239+
240+
- `utilities/InitArgs.html` - Validates and initializes arguments with type checking and
241+
defaults
242+
- `utilities/InitTypes.html` - Loads type definitions and merges structure-specific with
243+
global definitions
244+
- `data/structures/_arguments.yml` - Global argument definitions (type, default, options,
245+
etc.)
246+
- `data/structures/<name>.yml` - Structure-specific argument definitions for each shortcode
247+
or component
248+
249+
**How it works:**
250+
251+
1. **Structure definition inheritance:** Each shortcode/component has a structure file
252+
(e.g., `example.yml`) that defines its arguments. These definitions automatically
253+
inherit from the global `_arguments.yml` file.
254+
255+
2. **Automatic camelCase conversion:** Hyphenated argument names (e.g., `show-preview`)
256+
are automatically converted to camelCase (e.g., `showPreview`) for easier access in
257+
templates. Both versions are available: `$args.show-preview` and `$args.showPreview`.
258+
259+
3. **Default value application:** Arguments with `default` or `config` fields in their
260+
definition are automatically initialized with those values if not provided by the user.
261+
262+
4. **Type validation:** Arguments are validated against their declared types. The system
263+
automatically casts between compatible types (e.g., string `"true"` to boolean `true`).
264+
265+
5. **Deprecation warnings:** Deprecated arguments (marked with `deprecated` field) trigger
266+
warnings when used, guiding users to the preferred alternative.
267+
268+
**Structure definition format:**
269+
270+
Structure files follow the DRY principle with clear separation of concerns:
271+
272+
**Global definitions** (`mod-utils/data/structures/_arguments.yml`):
273+
274+
- `type` - Argument data type
275+
- `default` - Default value (if applicable)
276+
- `options` - Valid values for select types
277+
- `comment` - Description of what the argument does
278+
279+
**Component-specific definitions** (`data/structures/<name>.yml`):
280+
281+
- `optional` - Whether argument is required or optional
282+
- `deprecated` - Version when argument was deprecated (component-specific)
283+
- `alternative` - Replacement argument when deprecated (component-specific)
284+
- `release` - Version when argument was introduced (component-specific)
285+
286+
```yaml
287+
# Component structure file (e.g., data/structures/preview.yml)
288+
comment: >-
289+
Renders a live URL preview with switchable device views.
290+
arguments:
291+
url:
292+
optional: false
293+
release: v1.0.0
294+
device:
295+
optional: true
296+
release: v1.0.0
297+
heading:
298+
optional: true
299+
release: v1.0.0
300+
old-param:
301+
optional: true
302+
deprecated: v1.1.0
303+
alternative: device
304+
```
305+
306+
```yaml
307+
# Global argument definitions (mod-utils/data/structures/_arguments.yml)
308+
arguments:
309+
url:
310+
type: string
311+
comment: >-
312+
Address of the link destination, either a local reference or an external
313+
address. Include the `scheme` when referencing an external address.
314+
device:
315+
type: select
316+
default: desktop
317+
comment: >-
318+
Device view to display by default in preview component. Determines the
319+
initial iframe dimensions and active tab.
320+
options:
321+
values:
322+
- desktop
323+
- tablet
324+
- mobile
325+
heading:
326+
type: heading
327+
comment: >-
328+
Heading of the content block, including a preheading and content element.
329+
```
330+
331+
**When to add to _arguments.yml:**
332+
333+
- New argument used by multiple components - add full type definition to global file
334+
- Component-specific argument - can define inline in structure file (but prefer global for reusability)
335+
- Override default or add options - define inline in structure file (inherits base type from global)
336+
337+
**Common pitfall - Boolean argument handling:**
338+
339+
When accessing boolean arguments that could be explicitly set to `false`, **DO NOT use the
340+
`or` operator** for fallback logic:
341+
342+
```hugo
343+
{{/* WRONG - or operator treats false as falsy and skips it */}}
344+
{{- $showPreview := or $args.showPreview $args.show_preview }}
345+
346+
{{/* CORRECT - directly access the camelCase version */}}
347+
{{- $showPreview := $args.showPreview }}
348+
```
349+
350+
The `or` operator returns the first truthy value, so `or false <fallback>` will skip
351+
`false` and return the fallback instead of honoring the explicit `false` value.
352+
353+
**Best practices:**
354+
355+
- Always use `InitArgs` at the start of shortcodes and partials to validate arguments
356+
- Define structure files in `data/structures/` for all shortcodes and components
357+
- **Structure files should only reference arguments by name** - type definitions go in `_arguments.yml`
358+
- **Only include arguments actively used by the component partial** - section arguments (like `id`, `background`, `width`, `justify`, `wrapper`, `fluid`, `theme`, `cover`, `overlay-mode`, `section-class`, `bg-class`) are handled by the Bookshop section wrapper and should NOT be in the component's structure file
359+
- Add new argument types to `mod-utils/data/structures/_arguments.yml` for reuse across components
360+
- Mark `deprecated`, `alternative`, and `release` in **component structure files** (component-specific metadata)
361+
- Use hyphenated names for new arguments (e.g., `show-preview` not `show_preview`)
362+
- Access arguments via their camelCase versions (e.g., `$args.showPreview`)
363+
364+
**Example usage in a shortcode:**
365+
366+
```hugo
367+
{{/* Initialize and validate arguments */}}
368+
{{- $args := partial "utilities/InitArgs.html" (dict
369+
"structure" "example"
370+
"args" .Params
371+
"named" .IsNamedParams
372+
"group" "shortcode"
373+
) -}}
374+
375+
{{/* Check for errors/warnings */}}
376+
{{- if or $args.err $args.warnmsg -}}
377+
{{- partial (cond $args.err "utilities/LogErr.html" "utilities/LogWarn.html") (dict
378+
"partial" "shortcodes/example.html"
379+
"msg" "Invalid arguments"
380+
"details" ($args.errmsg | append $args.warnmsg)
381+
"file" page.File
382+
"position" .Position
383+
)}}
384+
{{- end -}}
385+
386+
{{/* Access arguments using camelCase */}}
387+
{{- $showPreview := $args.showPreview }}
388+
{{- $showMarkup := $args.showMarkup }}
389+
```
390+
191391
### Content Security Policy
192392

193393
CSP headers are auto-generated via Hugo's segments feature. The theme includes `mod-csp` for Content Security Policy management. Headers are defined in `netlify.toml` and generated via `npm run build:headers`.

assets/js/preview.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Preview Component - Auto-switch device on resize
3+
* Automatically switches to next available device when current device panel is hidden
4+
*/
5+
6+
function initPreviewAutoSwitch () {
7+
const previewSections = document.querySelectorAll('section.preview')
8+
9+
previewSections.forEach(section => {
10+
const buttons = section.querySelectorAll('.preview-controls .btn-group .btn')
11+
12+
if (buttons.length === 0) return
13+
14+
/**
15+
* Check if active button is hidden and switch to next available
16+
*/
17+
const checkAndSwitchDevice = () => {
18+
// Find currently active button
19+
const activeButton = Array.from(buttons).find(btn => btn.classList.contains('active'))
20+
21+
if (!activeButton) return
22+
23+
// Check if active button is hidden by CSS
24+
const isHidden = window.getComputedStyle(activeButton).display === 'none'
25+
26+
if (isHidden) {
27+
// Find all visible buttons
28+
const visibleButtons = Array.from(buttons).filter(btn =>
29+
window.getComputedStyle(btn).display !== 'none'
30+
)
31+
32+
if (visibleButtons.length > 0) {
33+
// Switch to first visible button (desktop -> tablet -> mobile priority)
34+
visibleButtons[0].click()
35+
}
36+
}
37+
}
38+
39+
// Debounced resize handler (avoid excessive checks)
40+
let resizeTimer
41+
window.addEventListener('resize', () => {
42+
clearTimeout(resizeTimer)
43+
resizeTimer = setTimeout(checkAndSwitchDevice, 150)
44+
})
45+
46+
// Initial check on page load
47+
checkAndSwitchDevice()
48+
})
49+
}
50+
51+
// Initialize on load (resize handler is set up inside initPreviewAutoSwitch)
52+
window.addEventListener('load', () => { initPreviewAutoSwitch() })

assets/scss/app-dart.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
@import "components/toast.scss";
4848
@import "components/timeline.scss";
4949
@import "components/toc.scss";
50+
@import "components/tooltip.scss";
5051
@import "components/video.scss";
5152
@import "common/animation.scss";
5253
@import "common/masonry.scss";

assets/scss/app.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
@import "components/toast.scss";
4646
@import "components/timeline.scss";
4747
@import "components/toc.scss";
48+
@import "components/tooltip.scss";
4849
@import "components/video.scss";
4950
@import "common/animation.scss";
5051
@import "common/masonry.scss";

assets/scss/components/_card.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@
7575
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.06);
7676
}
7777

78+
.card-minimal {
79+
border: none;
80+
font-weight: bold;
81+
82+
&:hover,
83+
&:focus {
84+
text-decoration: underline;
85+
}
86+
}
87+
7888
// stylelint-disable annotation-no-unknown
7989
.card-body-link {
8090
color: $body-color if($enable-important-utilities, !important, null);

assets/scss/components/_docs.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
border-top-left-radius: #{$theme-border-radius};
44
border-top-right-radius: #{$theme-border-radius};
55
margin-left: #{$theme-border-radius};
6-
7-
&:hover,
8-
&:focus {
9-
border-bottom: 0;
10-
}
116
}
127

138
.docs-panel,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.btn-tooltip a {
2+
text-decoration: none;
3+
}
4+
5+
.btn-tooltip a[href] {
6+
color: var(--bs-link-color) !important;
7+
text-decoration-color: var(--bs-link-color) !important;
8+
}

config/_default/menus/menus.en.toml

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,3 @@
2525
pre = "fab medium"
2626
url = "https://medium.com/"
2727
weight = 30
28-
29-
# toml-docs-start sample-navigation
30-
[[sample]]
31-
name = "Blog"
32-
pageRef = "/blog/"
33-
weight = 10
34-
35-
[[sample]]
36-
name = "Projects"
37-
pageRef = "/projects/"
38-
weight = 20
39-
40-
[[sample]]
41-
name = "Sample project"
42-
pageRef = "/projects/sample-project/"
43-
parent = "Projects"
44-
weight = 1
45-
46-
[[sample]]
47-
name = "Another project"
48-
pageRef = "/projects/another-project/"
49-
parent = "Projects"
50-
weight = 2
51-
# toml-docs-end sample-navigation

config/_default/params.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@
286286
# provide default hero settings
287287
[modules.bookshop.hero]
288288
align = "start"
289-
# backdrop = "/assets/img/nat-9l98kFByiao-unsplash.jpg"
290289
overlayMode = "dark"
291290
section = true
292291
default = ["section"]

0 commit comments

Comments
 (0)