alerts, toast amd breadcrum
This commit is contained in:
parent
aa651083cd
commit
1f95f86829
12 changed files with 525 additions and 1 deletions
|
|
@ -69,6 +69,31 @@ func TablerNavbar(c *core.Context) *core.Response {
|
|||
return c.Response.Template("tabler_default.html", data)
|
||||
}
|
||||
|
||||
// TablerComponents renders a page with alerts, breadcrumbs, and toasts.
|
||||
// Uses the composition pattern: embeds TablerPageData and adds Components.
|
||||
func TablerComponents(c *core.Context) *core.Response {
|
||||
type componentsPageData struct {
|
||||
TablerPageData
|
||||
Components FormtablerComponentsPage
|
||||
}
|
||||
data := componentsPageData{
|
||||
TablerPageData: TablerPageData{
|
||||
PageTitle: "UI Components",
|
||||
PageDescription: "Alerts, breadcrumbs and toasts demo",
|
||||
ShowTopbar: true,
|
||||
Sidebar: false,
|
||||
PageHeader: "Components",
|
||||
PagePretitle: "UI Elements",
|
||||
UserName: "Jane Doe",
|
||||
UserRole: "Administrator",
|
||||
NavbarMenu: SampleNavbarMenu(),
|
||||
Content: template.HTML(""),
|
||||
},
|
||||
Components: SampleComponents(),
|
||||
}
|
||||
return c.Response.Template("tabler_components.html", data)
|
||||
}
|
||||
|
||||
// TablerCards renders a page with card component demos.
|
||||
// Uses the composition pattern: embeds TablerPageData and adds Cards.
|
||||
func TablerCards(c *core.Context) *core.Response {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,103 @@ func SampleNavbarMenu() TablerMenu {
|
|||
}
|
||||
}
|
||||
|
||||
// SampleComponents returns sample data for alerts, breadcrumbs, and toasts.
|
||||
func SampleComponents() FormtablerComponentsPage {
|
||||
return FormtablerComponentsPage{
|
||||
Alerts: []FormtablerAlert{
|
||||
{
|
||||
Type: "success",
|
||||
Title: "Success alert!",
|
||||
ShowClose: true,
|
||||
},
|
||||
{
|
||||
Type: "warning",
|
||||
Title: "Warning alert with description",
|
||||
Description: "This is a warning alert with additional description text to provide more context to the user.",
|
||||
List: []string{"Item one is important", "Item two requires attention", "Item three is optional"},
|
||||
},
|
||||
{
|
||||
Type: "danger",
|
||||
Title: "Danger alert",
|
||||
Action: "Undo",
|
||||
Buttons: true,
|
||||
},
|
||||
{
|
||||
Type: "info",
|
||||
Title: "Info alert with link",
|
||||
Link: "Learn more",
|
||||
ShowClose: true,
|
||||
},
|
||||
{
|
||||
Type: "success",
|
||||
Title: "Important alert",
|
||||
Important: true,
|
||||
ShowClose: true,
|
||||
},
|
||||
{
|
||||
Type: "warning",
|
||||
Title: "Minor alert variant",
|
||||
Minor: true,
|
||||
},
|
||||
},
|
||||
Breadcrumbs: []FormtablerBreadcrumb{
|
||||
{
|
||||
Items: []FormtablerBreadcrumbItem{
|
||||
{Title: "Home", Link: "/", HomeIcon: true},
|
||||
{Title: "Library", Link: "/library"},
|
||||
{Title: "Data"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Separator: "arrows",
|
||||
Items: []FormtablerBreadcrumbItem{
|
||||
{Title: "Dashboard", Link: "/"},
|
||||
{Title: "Components", Link: "/components"},
|
||||
{Title: "Alerts"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Separator: "dots",
|
||||
Items: []FormtablerBreadcrumbItem{
|
||||
{Title: "Home", Link: "/", HomeIcon: true},
|
||||
{Title: "Settings", Link: "/settings"},
|
||||
{Title: "Profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Toasts: []FormtablerToast{
|
||||
{
|
||||
ID: "simple",
|
||||
Show: true,
|
||||
PersonName: "Paweł Kuna",
|
||||
PersonSrc: "/static/avatars/000m.jpg",
|
||||
Date: "2 mins ago",
|
||||
Text: "Hello, world! This is a toast message.",
|
||||
},
|
||||
{
|
||||
ID: "avatar-toast",
|
||||
Show: true,
|
||||
PersonName: "Jeffie Lewzey",
|
||||
PersonSrc: "/static/avatars/052f.jpg",
|
||||
Date: "5 mins ago",
|
||||
Text: "Your report has been generated successfully.",
|
||||
},
|
||||
{
|
||||
ID: "cookies",
|
||||
Show: true,
|
||||
Date: "just now",
|
||||
Cookies: true,
|
||||
},
|
||||
{
|
||||
ID: "no-header",
|
||||
Show: true,
|
||||
HideHeader: true,
|
||||
Text: "This toast has no header — just a plain message body.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SampleCards returns 6 sample cards showing different variants.
|
||||
func SampleCards() []FormtablerCard {
|
||||
defaultBody := &FormtablerCardBody{
|
||||
|
|
|
|||
|
|
@ -369,6 +369,48 @@ type FormtablerCardBody struct {
|
|||
ButtonLink string
|
||||
}
|
||||
|
||||
// FormtablerAlert represents a single alert component.
|
||||
type FormtablerAlert struct {
|
||||
Type string // "success", "warning", "danger", "info"
|
||||
Title string
|
||||
Description string
|
||||
List []string // if set, renders as <ul class="alert-list">
|
||||
Important bool
|
||||
Minor bool
|
||||
ShowClose bool
|
||||
Action string // "Action" button text
|
||||
Link string // "Link" anchor text
|
||||
Buttons bool // "Okay" / "Cancel" buttons
|
||||
Avatar bool
|
||||
Class string
|
||||
}
|
||||
|
||||
// FormtablerBreadcrumbItem represents a single item in the breadcrumb trail.
|
||||
type FormtablerBreadcrumbItem struct {
|
||||
Title string
|
||||
Link string // empty for the last (active) item
|
||||
HomeIcon bool // render home icon instead of text for the first item
|
||||
}
|
||||
|
||||
// FormtablerBreadcrumb represents a breadcrumb navigation component.
|
||||
type FormtablerBreadcrumb struct {
|
||||
Items []FormtablerBreadcrumbItem
|
||||
Separator string // optional: "dots", "arrows", "bullets"
|
||||
Class string
|
||||
}
|
||||
|
||||
// FormtablerToast represents a toast notification component.
|
||||
type FormtablerToast struct {
|
||||
ID string
|
||||
Show bool
|
||||
HideHeader bool
|
||||
PersonName string
|
||||
PersonSrc string
|
||||
Date string
|
||||
Text string
|
||||
Cookies bool // renders cookie consent variant
|
||||
}
|
||||
|
||||
// FormtablerCardProgress represents a progress bar in the card.
|
||||
type FormtablerCardProgress struct {
|
||||
Percent int
|
||||
|
|
@ -431,6 +473,14 @@ type FormtablerFormElementsPage struct {
|
|||
ValidationStates FormtablerValidationStates
|
||||
}
|
||||
|
||||
// FormtablerComponentsPage is the page-specific struct for the combined demo
|
||||
// showing alerts, toasts, and breadcrumbs.
|
||||
type FormtablerComponentsPage struct {
|
||||
Alerts []FormtablerAlert
|
||||
Breadcrumbs []FormtablerBreadcrumb
|
||||
Toasts []FormtablerToast
|
||||
}
|
||||
|
||||
// TablerPageData holds the common data for all tabler pages.
|
||||
// It should NOT contain component-specific fields like tables or forms.
|
||||
// Add those by creating a page-specific struct that embeds TablerPageData
|
||||
|
|
|
|||
|
|
@ -72,5 +72,6 @@ func registerRoutes() {
|
|||
controller.Get("/tablertable", controllers.TablerTables)
|
||||
controller.Get("/tablerformelements", controllers.TablerFormElements)
|
||||
controller.Get("/tablercards", controllers.TablerCards)
|
||||
controller.Get("/tablercomponents", controllers.TablerComponents)
|
||||
|
||||
}
|
||||
|
|
|
|||
40
storage/templates/tabler/includes/alert.html
Normal file
40
storage/templates/tabler/includes/alert.html
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{{define "tabler_alert"}}
|
||||
{{$a := .}}
|
||||
{{$icon := $a.Type}}
|
||||
{{if eq $a.Type "success"}}{{$icon = "check"}}{{else if eq $a.Type "warning"}}{{$icon = "alert-triangle"}}{{else if eq $a.Type "danger"}}{{$icon = "alert-circle"}}{{else if eq $a.Type "info"}}{{$icon = "info-circle"}}{{end}}
|
||||
<div class="alert{{if $a.Important}} alert-important{{else if $a.Minor}} alert-minor{{end}} alert-{{$a.Type}}{{if $a.ShowClose}} alert-dismissible{{end}}{{if $a.Avatar}} alert-avatar{{end}}{{if $a.Class}} {{$a.Class}}{{end}}" role="alert">
|
||||
<div class="alert-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon alert-icon">
|
||||
{{if eq $icon "check"}}<path d="M5 12l5 5l10 -10"></path>
|
||||
{{else if eq $icon "alert-triangle"}}<path d="M12 9v4"></path><path d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.871l-8.106 -13.534a1.914 1.914 0 0 0 -3.274 0z"></path><path d="M12 16h.01"></path>
|
||||
{{else if eq $icon "alert-circle"}}<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"></path><path d="M12 8v4"></path><path d="M12 16h.01"></path>
|
||||
{{else if eq $icon "info-circle"}}<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"></path><path d="M12 16v-4"></path><path d="M12 8h.01"></path>
|
||||
{{end}}
|
||||
</svg>
|
||||
</div>
|
||||
{{if or $a.Description $a.List}}
|
||||
<div>
|
||||
<h4 class="alert-heading">{{defaultVal "This is a custom alert box!" $a.Title}}</h4>
|
||||
<div class="alert-description">
|
||||
{{$a.Description}}
|
||||
{{if $a.List}}
|
||||
<ul class="alert-list">
|
||||
{{range $a.List}}<li>{{.}}</li>{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{defaultVal "This is a custom alert box!" $a.Title}}
|
||||
{{if $a.Action}}<a href="#" class="alert-action">{{$a.Action}}</a>{{end}}
|
||||
{{if $a.Link}}<a href="#" class="alert-link">{{$a.Link}}</a>{{end}}
|
||||
{{end}}
|
||||
{{if $a.Buttons}}
|
||||
<div class="btn-list">
|
||||
<a href="#" class="btn btn-{{$a.Type}}">Okay</a>
|
||||
<a href="#" class="btn">Cancel</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if $a.ShowClose}}<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
22
storage/templates/tabler/includes/breadcrumb.html
Normal file
22
storage/templates/tabler/includes/breadcrumb.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{{define "tabler_breadcrumb"}}
|
||||
{{$b := .}}
|
||||
<nav aria-label="Breadcrumb">
|
||||
<ol class="breadcrumb{{if $b.Class}} {{$b.Class}}{{end}}{{if $b.Separator}} breadcrumb-{{$b.Separator}}{{end}}">
|
||||
{{range $i, $item := $b.Items}}
|
||||
{{if $item.Link}}
|
||||
<li class="breadcrumb-item">
|
||||
{{if and (eq $i 0) $item.HomeIcon}}
|
||||
<a href="{{$item.Link}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path d="M5 12l-2 0l9 -9l9 9l-2 0"></path><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"></path><path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"></path></svg>
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="{{$item.Link}}">{{$item.Title}}</a>
|
||||
{{end}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{$item.Title}}</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ol>
|
||||
</nav>
|
||||
{{end}}
|
||||
36
storage/templates/tabler/includes/components_content.html
Normal file
36
storage/templates/tabler/includes/components_content.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{{define "tabler_components_content"}}
|
||||
{{$c := .}}
|
||||
|
||||
{{if hasField $c "Alerts"}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title">Alerts</h3></div>
|
||||
<div class="card-body">
|
||||
{{range $c.Alerts}}
|
||||
{{template "tabler_alert" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if hasField $c "Breadcrumbs"}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title">Breadcrumbs</h3></div>
|
||||
<div class="card-body">
|
||||
{{range $c.Breadcrumbs}}
|
||||
{{template "tabler_breadcrumb" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if hasField $c "Toasts"}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title">Toasts</h3></div>
|
||||
<div class="card-body">
|
||||
{{range $c.Toasts}}
|
||||
{{template "tabler_toast" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
23
storage/templates/tabler/includes/toast.html
Normal file
23
storage/templates/tabler/includes/toast.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{{define "tabler_toast"}}
|
||||
{{$t := .}}
|
||||
<div class="toast{{if $t.Show}} show{{end}}" id="toast-{{defaultVal "simple" $t.ID}}" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
|
||||
{{if not $t.HideHeader}}
|
||||
<div class="toast-header">
|
||||
<span class="avatar avatar-xs me-2" style="background-image: url({{defaultVal "/public/static/avatars/000m.jpg" $t.PersonSrc}})"></span>
|
||||
<strong class="me-auto">{{defaultVal "Jane Doe" $t.PersonName}}</strong>
|
||||
<small>{{defaultVal "11 mins ago" $t.Date}}</small>
|
||||
<button type="button" class="ms-2 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="toast-body">
|
||||
{{if $t.Cookies}}
|
||||
🍪 Our site uses cookies. By continuing to use our site, you agree to our Cookie Policy.
|
||||
<div class="mt-2 pt-2 border-top">
|
||||
<a href="#" class="btn btn-primary btn-sm">I understand</a>
|
||||
</div>
|
||||
{{else}}
|
||||
{{defaultVal "Hello, world! This is a toast message." $t.Text}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -32,6 +32,8 @@
|
|||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if hasField . "Components"}}
|
||||
{{template "tabler_components_content" .Components}}
|
||||
{{else if hasField . "Tables"}}
|
||||
{{range .Tables}}
|
||||
<div class="card{{if .CardClass}} {{.CardClass}}{{end}}">
|
||||
|
|
|
|||
1
storage/templates/tabler_components.html
Normal file
1
storage/templates/tabler_components.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{template "default_layout" .}}
|
||||
1
storage/templates/tabler_homepage.html
Normal file
1
storage/templates/tabler_homepage.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{template "homepage_layout" .}}
|
||||
226
template-helpers.md
Normal file
226
template-helpers.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# Go Template Helpers
|
||||
|
||||
Custom `html/template` FuncMap extensions that bring Liquid-like expressiveness to Go server-side templates.
|
||||
|
||||
---
|
||||
|
||||
## String Helpers
|
||||
|
||||
### `capitalize`
|
||||
|
||||
Uppercases the first letter of a string and lowercases the rest.
|
||||
|
||||
```html
|
||||
<h2>{{capitalize .Title}}</h2>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `truncate`
|
||||
|
||||
Cuts a string to at most `n` characters and appends `…` if trimmed. Designed for pipeline use — pass `n` first.
|
||||
|
||||
```html
|
||||
<p>{{.Excerpt | truncate 120}}</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `prepend`
|
||||
|
||||
Adds a prefix to the beginning of a string. Designed for pipeline use — pass the prefix first.
|
||||
|
||||
```html
|
||||
<a href="{{.Slug | prepend "/articles/"}}">Read more</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `strAppend`
|
||||
|
||||
Adds a suffix to the end of a string. Designed for pipeline use — pass the suffix first.
|
||||
|
||||
```html
|
||||
<a href="{{.Slug | strAppend ".html"}}">Read more</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `split`
|
||||
|
||||
Divides a string into a slice of substrings by a separator. Designed for pipeline use — pass the separator first. Useful combined with `range` or `join`.
|
||||
|
||||
```html
|
||||
{{range split "," .TagString}}
|
||||
<span class="tag">{{.}}</span>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `join`
|
||||
|
||||
Concatenates a string slice into a single string with a separator.
|
||||
|
||||
```html
|
||||
<p class="tags">{{join .Tags " · "}}</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Number Helpers
|
||||
|
||||
### `fmtNumber`
|
||||
|
||||
Formats an integer or float with thousands separators using the English locale.
|
||||
|
||||
```html
|
||||
<span>{{fmtNumber .Views}} views</span>
|
||||
<span>$ {{fmtNumber .Price}}</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Date & Time Helpers
|
||||
|
||||
### `fmtDate`
|
||||
|
||||
Formats a `time.Time` value using a named layout or any custom Go layout string.
|
||||
|
||||
| Named layout | Output example |
|
||||
|---|---|
|
||||
| `"short"` | `02 Jan 2006` |
|
||||
| `"long"` | `02 January 2006` |
|
||||
| `"iso"` | `2006-01-02` |
|
||||
| `"datetime"` | `02 Jan 2006 15:04` |
|
||||
|
||||
```html
|
||||
<time>{{fmtDate .PublishedAt "short"}}</time>
|
||||
<time>{{fmtDate .PublishedAt "02/01/2006"}}</time>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `timeAgo`
|
||||
|
||||
Returns a human-readable relative time string from now (`"just now"`, `"3 hours ago"`, `"2 days ago"`, etc.).
|
||||
|
||||
```html
|
||||
<span class="meta">Published {{timeAgo .PublishedAt}}</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Collection Helpers
|
||||
|
||||
### `first`
|
||||
|
||||
Returns the first element of a slice, or `nil` if the slice is empty.
|
||||
|
||||
```html
|
||||
{{with first .Articles}}
|
||||
<h2>{{capitalize .Title}}</h2>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `last`
|
||||
|
||||
Returns the last element of a slice, or `nil` if the slice is empty.
|
||||
|
||||
```html
|
||||
{{with last .Articles}}
|
||||
<p>Latest: {{capitalize .Title}}</p>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `sliceOf`
|
||||
|
||||
Returns a sub-range of a slice from index `start` (inclusive) to `end` (exclusive).
|
||||
|
||||
```html
|
||||
{{range sliceOf .Articles 0 3}}
|
||||
<li>{{capitalize .Title}}</li>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `contains`
|
||||
|
||||
Reports whether an item is present in a slice, or a substring exists within a string.
|
||||
|
||||
```html
|
||||
{{if contains .Tags "go"}}
|
||||
<span class="badge">Go</span>
|
||||
{{end}}
|
||||
|
||||
{{if contains .Bio "engineer"}}
|
||||
<p>Engineering post</p>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logic Helpers
|
||||
|
||||
### `defaultVal`
|
||||
|
||||
Returns a fallback value if the given value is nil or its zero value.
|
||||
|
||||
```html
|
||||
<p>{{defaultVal "No subtitle" .Subtitle}}</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ternary`
|
||||
|
||||
Returns `trueVal` if the condition is true, `falseVal` otherwise. Pass the true value first, then the false value, then the condition.
|
||||
|
||||
```html
|
||||
<span>{{ternary "Active" "Inactive" .IsActive}}</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `coalesce`
|
||||
|
||||
Returns the first non-empty, non-nil value from a list of arguments.
|
||||
|
||||
```html
|
||||
<h3>{{coalesce .Nickname .FullName "Anonymous"}}</h3>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Struct Helpers
|
||||
|
||||
### `hasField`
|
||||
|
||||
Reports whether a struct has a field with the given name. Useful for rendering shared templates across different data types.
|
||||
|
||||
```html
|
||||
{{if hasField . "Subtitle"}}
|
||||
<p class="subtitle">{{.Subtitle}}</p>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pipeline Chaining
|
||||
|
||||
Helpers designed for pipeline use (`truncate`, `prepend`, `strAppend`, `split`) accept their configuration argument first and the input string last, so they compose naturally with `|`.
|
||||
|
||||
```html
|
||||
{{/* chain multiple helpers */}}
|
||||
<td>{{.Title | capitalize | truncate 40}}</td>
|
||||
|
||||
{{/* build a URL from a slug */}}
|
||||
<a href="{{.Slug | prepend "/blog/" | strAppend ".html"}}">
|
||||
{{.Title | capitalize}}
|
||||
</a>
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue