Skip to content

Commit

Permalink
Add partial coding conventions
Browse files Browse the repository at this point in the history
  • Loading branch information
markdumay committed Jan 3, 2024
1 parent 72aaf59 commit a23c9be
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 1 deletion.
3 changes: 3 additions & 0 deletions config/_default/params.toml
Expand Up @@ -274,6 +274,7 @@
hinode = "https://gethinode.com"
hinode_demo = "https://demo.gethinode.com/"
hinode_fr = "https://demo.gethinode.com/fr/"
hinode_release_v0_22_0 = "https://github.com/gethinode/hinode/releases/tag/v0.22.0-beta7"
hinode_version_demo = "https://version-demo.gethinode.com/"
html_codeguide = "https://codeguide.co/#html"
hugo = "https://gohugo.io"
Expand Down Expand Up @@ -308,6 +309,7 @@
hugo_pipes = "https://gohugo.io/hugo-pipes/introduction"
hugo_postprocess = "https://gohugo.io/hugo-pipes/postprocess"
hugo_release_v0_109_0 = "https://github.com/gohugoio/hugo/releases/tag/v0.109.0"
hugo_scratch = "https://gohugo.io/methods/shortcode/scratch"
hugo_sections = "https://gohugo.io/content-management/sections"
hugo_shortcodes = "https://gohugo.io/content-management/shortcodes"
hugo_structure = "https://gohugo.io/content-management/organization"
Expand Down Expand Up @@ -366,6 +368,7 @@
repository_mod_katex = "https://github.com/gethinode/mod-katex"
repository_mod_leaflet = "https://github.com/gethinode/mod-leaflet"
repository_mod_template = "https://github.com/gethinode/mod-template"
repository_mod_utils = "https://github.com/gethinode/mod-utils"
repository_leaflet = "https://github.com/gethinode/leaflet-demo"
repository_releases = "https://github.com/gethinode/hinode/releases/"
repository_tag = "https://github.com/gethinode/hinode/releases/tag/"
Expand Down
93 changes: 93 additions & 0 deletions content/en/docs/latest/advanced-settings/partial-development.md
@@ -0,0 +1,93 @@
---
title: Partial development
description: Develop custom partials and shortcodes following Hinode's coding conventions.
date: 2024-01-03
layout: docs
---

Hinode supports more than 30 shortcodes. Many of these shortcodes wrap an predefined Bootstrap component, such as the {{< link "docs/components/button" />}} or {{< link "docs/components/tooltip" />}}. Custom shortcodes include a {{< link "docs/components/command-prompt" />}}, {{< link "docs/components/image" />}}, and {{< link "docs/components/timeline" />}}. Some of these components are maintained in a separate module, such as the {{< link "docs/components/animation" />}} or {{< link "docs/components/map" />}}. Hinode follows several conventions to standardize and streamline the development of shortcodes and partials. You are encouraged to use the same conventions, especially when contributing your own code for sharing.

## Shared partials

Hugo supports two kinds of reusable components, being partials and shortcodes. A shortcode can be referenced from within content files, such as Markdown. Partials can be referenced by layout files, other partials, and shortcodes too. You cannot reference a shortcode from a partial though. To enable reuse, Hinode has shifted the bulk of the implementation of many of its shortcodes to separate partials. These partials are maintained in the `layouts/partials/assets` folder. The related shortcode then simply references the partial.

As an example, consider the implementation of the {{< link "docs/components/breadcrumb" />}}. Hinode adds a breadcrumb to all pages (except the homepage) if enabled in the {{< link "docs/configuration/navigation#basic-configuration" >}}site parameters{{< /link >}}. The implementation is available in `layouts/partials/assets/breadcrumb.html`. The same component is also exposed as a shortcode, so it can be called from within a content page. The shortcode file `layouts/shortcodes/breadcrumb.html` includes the following statement to invoke the partial. The `page` argument passes the current page context to the underlying partial:

```go-template
{{ partial "assets/breadcrumb.html" (dict "page" .page) }}
```

## Nested shortcodes

Several shortcodes, such as the {{< link "docs/components/accordion" />}} and {{< link "docs/components/carousel" />}}, support the nesting of elements. For example, you can group multiple cards to align their heights. To enhance security, {{< link "docs/content/content-management#mixed-content" >}}Hinode does not process raw HTML content by default{{< /link >}}. However, the parent shortcode `card-group` does need to further process the HTML output generated by the individual cards. To faciliate this, Hinode uses {{< link "hugo_scratch" >}}scratch variables{{< /link >}} to pass trusted output from a child to its parent. These scratch variables are not accessible from within the content page, thus shielding them from any unwanted input.

Take a look at the `card` shortcode. It generates HTML content by invoking the underlying partial. If a parent is available (such as a `card-group` shortcode), it redirects or appends the partial output to the scratch variable `inner`. When no parent is available, the partial output is written to the default output stream instead. The partial output is trusted (note: the actual content processed as input by the `card` partial is **not trusted**) with the `safeHTML` pipeline instruction.

```go-template
{{ $output := partial "assets/card.html" (dict [...]) }}
{{ with .Parent }}
{{ $current := .Scratch.Get "inner" }}
{{ if $current }}
{{ .Scratch.Set "inner" (print $current $output) }}
{{ else }}
{{ .Scratch.Set "inner" $output }}
{{ end }}
{{ else }}
{{ print $output | safeHTML }}
{{ end }}
```

Next, the parent `card-group` shortcode reads the scratch variable `inner` and passes this as an argument to the `card-group` partial. Each of the child `card` shortcodes should have processed the inner content. If any content remains, the `card-group` shortcode raises a warning and skips this input for further processing.

```go-template
{{ $inner := .Scratch.Get "inner" }}
{{ $input := trim .Inner " \r\n" }}
{{ if $input }}
{{ $input = replace $input "\n" "\n " }}
{{ warnf "Unexpected inner content: %s\r\n %s" .Position $input }}
{{ end }}
{{ partial "assets/card-group.html" (dict "page" .Page "cards" $inner [...]) }}
```

## Argument validation

{{< release version="0.22.0-beta6" >}}

Most shortcodes support multiple arguments to configure their behavior and to refine their appearance. These shortcodes share many of these arguments with an underlying partial. Hinode uses a standardized approach to validate these arguments. All arguments are formally defined in a separate data structure file. Hinode uses the {{< abbr YAML >}} format by default, although several formats are supported. The partial `utilities/IsInvalidArgs.html` (provided by the {{< link "repository_mod_utils" >}}mod-utils module{{< /link >}}) then uses this specification to validate all arguments. Refer to the documentation to review the {{< link "docs/components/args#data-format" >}}supported data format{{< /link >}}.

Let's consider the following example. The {{< link "docs/components/toast" />}} shortcode displays a dismissable message in the bottom-right corner of the screen. We can trigger it by assigning its unique identifier to a button.

<!-- markdownlint-disable MD037 -->
{{< example lang="hugo" >}}
{{</* button toast="toast-example-1" */>}}Show toast{{</* /button */>}}

{{</* toast id="toast-example-1" header="First title" */>}}This is a toast message{{</* /toast */>}}
{{< /example >}}
<!-- markdownlint-enable MD037 -->

The toast shortcode displays the message `This is a toast message` provided as inner input. Additionally, it supports the following arguments:

{{< args structure="toast" group="shortcode" >}}

The toast shortcode invokes the underlying partial to render the actual HTML output. The partial supports similar arguments, but expects the inner content to be passed as argument `message` instead. The following file formalizes these specifications:

{{< file path="./_vendor/github.com/gethinode/hinode/data/structures/toast.yml" full="false" >}}

The shortcode uses the following code to validate its arguments, excluding the `message` argument that belongs to the `partial` group. When an error occurs, the shortcode logs an error message with a reference to the context `.Position`.

```go-template
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "toast" "args" .Params "group" "shortcode") }}
{{ errorf "Invalid arguments: %s" .Position -}}
{{ $error = true }}
{{ end }}
```

The underlying partial uses a similar call. Notable differences are the validated arguments (`.` instead of `.Params`) and the `group` (`partial` instead of `shortcode`). Partials are not aware of their context, so a generic error is logged instead.

```go-template
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "toast" "args" . "group" "partial") }}
{{- errorf "partial [assets/toast.html] - Invalid arguments" -}}
{{ $error = true }}
{{ end }}
```
6 changes: 5 additions & 1 deletion content/en/docs/latest/getting-started/contribute.md
@@ -1,7 +1,7 @@
---
title: Contribute
description: Contribute to the open-source development of Hinode.
date: 2023-10-21
date: 2024-01-03
aliases:
- "/docs/contribute/"
- "/contribute/"
Expand Down Expand Up @@ -93,6 +93,10 @@ Hinode supports Hugo modules to to provide a flexible and extensible modular fra

In general, run `npm run lint` before committing to ensure your changes follow our coding standards.

### Partials and shortcodes

{{< link "docs/advanced-settings/partial-development" >}}Follow the coding conventions for partial development{{< /link >}}.

### HTML

{{< link html_codeguide >}}Adhere to the Code Guide{{< /link >}}.
Expand Down
1 change: 1 addition & 0 deletions data/docs.yml
Expand Up @@ -97,6 +97,7 @@
- title: Styles
- title: Scripts
- title: Icons
- title: Partial development
- title: Module development
# - title: Metadata
- title: Server headers
Expand Down

0 comments on commit a23c9be

Please sign in to comment.