willamette 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rubocop.yml +54 -0
  4. data/CHANGELOG.md +14 -0
  5. data/CODE_OF_CONDUCT.md +92 -0
  6. data/Gemfile +15 -0
  7. data/Gemfile.lock +250 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +3 -0
  10. data/Rakefile +11 -0
  11. data/automations/components.automation.rb +123 -0
  12. data/automations/frontend.automation.rb +30 -0
  13. data/automations/layouts.automation.rb +122 -0
  14. data/bridgetown.automation.rb +7 -0
  15. data/components/willamette/back_to_top.css +35 -0
  16. data/components/willamette/back_to_top.js +32 -0
  17. data/components/willamette/code_element.css +10 -0
  18. data/components/willamette/code_element.js +49 -0
  19. data/components/willamette/header_navbar.dsd.css +19 -0
  20. data/components/willamette/header_navbar.rb +20 -0
  21. data/components/willamette/holy_grail_layout.dsd.css +81 -0
  22. data/components/willamette/holy_grail_layout.rb +17 -0
  23. data/components/willamette/pagination.erb +12 -0
  24. data/components/willamette/pagination.rb +7 -0
  25. data/components/willamette/post_item.css +102 -0
  26. data/components/willamette/post_item.rb +90 -0
  27. data/components/willamette/previous_next.erb +12 -0
  28. data/components/willamette/previous_next.rb +7 -0
  29. data/components/willamette/search_dialog.rb +18 -0
  30. data/components/willamette/search_dialog_element.js +90 -0
  31. data/content/search.erb +29 -0
  32. data/content/willamette/style-guide.md +93 -0
  33. data/layouts/willamette/default.erb +44 -0
  34. data/lib/willamette/builders/inspectors.rb +28 -0
  35. data/lib/willamette/builders/toc.rb +11 -0
  36. data/lib/willamette/locales/en.yml +22 -0
  37. data/lib/willamette/strategies/link.rb +20 -0
  38. data/lib/willamette/strategies/sidebar.rb +83 -0
  39. data/lib/willamette/version.rb +5 -0
  40. data/lib/willamette.rb +83 -0
  41. data/package-lock.json +303 -0
  42. data/package.json +22 -0
  43. data/setup.automation.rb +14 -0
  44. data/willamette.gemspec +30 -0
  45. metadata +143 -0
@@ -0,0 +1,122 @@
1
+ remove_file "src/_layouts/default.erb"
2
+ create_file "src/_layouts/default.erb" do <<~ERB
3
+ ---
4
+ layout: willamette/default
5
+ ---
6
+
7
+ <%= yield %>
8
+ ERB
9
+ end
10
+
11
+ remove_file "src/_layouts/page.erb"
12
+ create_file "src/_layouts/page.erb" do <<~ERB
13
+ ---
14
+ layout: default
15
+ ---
16
+
17
+ <article class="<%= data.willamette&.article_classes %>">
18
+
19
+ <h1><%= pipe(data.title) { strip_html | smartify } %></h1>
20
+
21
+ <%= yield %>
22
+
23
+ </article>
24
+ ERB
25
+ end
26
+
27
+ remove_file "src/_layouts/post.erb"
28
+ create_file "src/_layouts/post.erb" do <<~ERB
29
+ ---
30
+ layout: default
31
+ ---
32
+
33
+ <article class="<%= data.willamette&.article_classes %>">
34
+
35
+ <h1><%= pipe(data.title) { strip_html | smartify } %></h1>
36
+
37
+ <%
38
+ if data.image
39
+ image_path = data.image.is_a?(String) ? data.image : data.image.path
40
+ image_alt = data.image.is_a?(String) ? t("content.featured_post_image") : data.image.alt
41
+ image_caption = data.image.is_a?(String) ? nil : data.image.caption
42
+ %>
43
+ <figure class="full-main-<%= data.image_bleed ? "bleed" : "size" %>">
44
+ <img src="<%= image_path %>" alt="" />
45
+ <% if image_caption %>
46
+ <figcaption><%= markdownify image_caption %></figcaption>
47
+ <% end %>
48
+ </figure>
49
+ <% end %>
50
+
51
+ <%= yield %>
52
+
53
+ <hr />
54
+
55
+ <wa-icon class="article-metadata" name="newspaper"></wa-icon>
56
+ <p class="article-metadata"><article-author>by So and So</article-author></p>
57
+ <p class="article-metadata"><time><%= pipe(resource.date) { to_date | l(format: :long) } %></time></p>
58
+ <p class="article-metadata"><article-tags>#foo #BarBaz</article-tags></p>
59
+
60
+ </article>
61
+ ERB
62
+ end
63
+
64
+ create_file "src/_layouts/documentation.erb" do <<~ERB
65
+ ---
66
+ layout: default
67
+ ---
68
+
69
+ <article class="<%= data.willamette&.article_classes %>">
70
+
71
+ <h1><%= pipe(data.title) { strip_html | smartify } %></h1>
72
+
73
+ <%= yield %>
74
+
75
+ </article>
76
+
77
+ <hr style="margin-block-start: calc(var(--wa-content-spacing) * 2)" />
78
+
79
+ <%= render Willamette::PreviousNext.new(resource:) %>
80
+ ERB
81
+ end
82
+
83
+ inject_into_file "src/index.md", after: "# Welcome to your new Bridgetown website.\n" do <<~MARKDOWN
84
+
85
+ <p><wa-button variant="brand" href="/willamette/style-guide">View the Willamette Style Guide</wa-button></p>
86
+
87
+ ----
88
+ MARKDOWN
89
+ end
90
+
91
+ remove_file "src/posts.md"
92
+ create_file "src/blog.erb" do <<~ERB
93
+ ---
94
+ layout: page
95
+ title: Blog
96
+ exclude_from_pagefind: true
97
+ paginate:
98
+ collection: posts
99
+ willamette:
100
+ post_style: headline_only
101
+ ---
102
+
103
+ <ul class="layout-<%= data.willamette.display_layout %>">
104
+ <% paginator.each do |post| %>
105
+ <li>
106
+ <a href="<%= post.relative_url %>">
107
+ <%= render Willamette::PostItem.new(post:, post_style: data.willamette.post_style) %>
108
+ </a>
109
+ </li>
110
+ <% end %>
111
+ <% if paginator.resources.length < 2 %>
112
+ <li></li>
113
+ <% end %>
114
+ </ul>
115
+
116
+ <% if paginator.total_pages > 1 %>
117
+ <hr style="margin-block-start: calc(var(--wa-content-spacing) * 2)" />
118
+
119
+ <%= render Willamette::Pagination(paginator:) %>
120
+ <% end %>
121
+ ERB
122
+ end
@@ -0,0 +1,7 @@
1
+ say "🛠️ Installing Willamette..."
2
+
3
+ run "bundle add nokolexbor"
4
+ run "bundle add willamette --git=git@codeberg.org:bridgetownrb/willamette.git"
5
+ run "npm install git+ssh://git@codeberg.org/bridgetownrb/willamette.git"
6
+
7
+ apply "#{__dir__}/setup.automation.rb"
@@ -0,0 +1,35 @@
1
+ wll-back-to-top {
2
+ display: block;
3
+ pointer-events: none;
4
+ opacity: 0;
5
+ position: fixed;
6
+ z-index: 99;
7
+ bottom: 20px;
8
+ right: 1px;
9
+ width: 45px;
10
+ height: 45px;
11
+
12
+ translate: 10px 0px;
13
+ transition: all 0.3s;
14
+
15
+ &[active] {
16
+ translate: 0px 0px;
17
+ opacity: 1;
18
+ pointer-events: auto;
19
+ }
20
+
21
+ & button {
22
+ appearance: none;
23
+ padding: 10px 14px;
24
+ background: var(--wa-color-brand-fill-quiet);
25
+ border: 1px solid var(--wa-color-brand-border-quiet);
26
+ border-right: none;
27
+ border-radius: var(--wa-border-radius-m) 0px 0px var(--wa-border-radius-m);
28
+ color: var(--wll-color-brand);
29
+ box-shadow: -1px 0px 4px rgba(0,0,0,0.08), inset -5px 0px 2px rgba(0,0,0,0.05);
30
+
31
+ & wa-icon {
32
+ vertical-align: middle;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,32 @@
1
+ class BackToTopElement extends HTMLElement {
2
+ static {
3
+ customElements.define("wll-back-to-top", this)
4
+ }
5
+
6
+ connectedCallback() {
7
+ this._previousScrollPosition = window.scrollY
8
+ this._scroller = this.scrollHandler.bind(this)
9
+ document.addEventListener("scroll", this._scroller)
10
+ this.addEventListener("click", () => {
11
+ window.scrollTo({ top: 0, behavior: "smooth" })
12
+ this.removeAttribute("active")
13
+ })
14
+ }
15
+
16
+ disconnectedCallback() {
17
+ document.removeEventListener("scroll", this._scroller)
18
+ }
19
+
20
+ scrollHandler(_event) {
21
+ let newPosition = window.scrollY
22
+ window.requestAnimationFrame(() => {
23
+ if (newPosition > 400 && newPosition < this._previousScrollPosition - 100) {
24
+ this._previousScrollPosition = newPosition;
25
+ this.removeAttribute("active")
26
+ } else if (newPosition > this._previousScrollPosition + 100) {
27
+ this._previousScrollPosition = newPosition;
28
+ this.setAttribute("active", true)
29
+ }
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,10 @@
1
+ wll-code {
2
+ display: block;
3
+ margin-block-end: var(--wa-content-spacing);
4
+
5
+ pre {
6
+ border-radius: 0;
7
+ background: color-mix(in display-p3, var(--wa-color-brand-fill-quiet), color-mix(in display-p3, var(--wa-color-neutral-fill-quiet), transparent 70%) 70%);
8
+ overflow: auto;
9
+ }
10
+ }
@@ -0,0 +1,49 @@
1
+ class CodeElement extends HTMLElement {
2
+ static {
3
+ customElements.define("wll-code", this)
4
+ }
5
+
6
+ constructor() {
7
+ super()
8
+
9
+ this.attachShadow({ mode: "open" })
10
+ }
11
+
12
+ connectedCallback() {
13
+ if (document.body.classList.contains("wll-code-dark")) this.classList.add("wa-dark")
14
+
15
+ this.shadowRoot.innerHTML = `
16
+ <style>
17
+ :host {
18
+ border: 1px solid var(--wa-color-brand-border-quiet);
19
+ border-radius: var(--wa-border-radius-m);
20
+ overflow: hidden;
21
+ background: var(--wll-main-background);
22
+ }
23
+
24
+ header {
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: center;
28
+ border-top-left-radius: var(--wa-border-radius-m);
29
+ border-top-right-radius: var(--wa-border-radius-m);
30
+ border-bottom: 1px solid var(--wa-color-brand-border-quiet);
31
+ background: var(--wa-color-brand-fill-quiet);
32
+ font-size: var(--wa-font-size-s);
33
+ text-transform: uppercase;
34
+ padding-block: 2px;
35
+ padding-inline: var(--wa-space-s);
36
+ font-weight: var(--wa-font-weight-bold);
37
+ }
38
+ </style>
39
+
40
+ <header><span>${this.getAttribute("class").split(" ")[0].split("-")[1]}</span> <slot name="copy-button"></slot></header>
41
+ <slot></slot>
42
+ `
43
+ this.firstElementChild.id = crypto.randomUUID()
44
+ const copyButton = document.createElement("wa-copy-button")
45
+ copyButton.slot = "copy-button"
46
+ copyButton.from = this.firstElementChild.id
47
+ this.append(copyButton)
48
+ }
49
+ }
@@ -0,0 +1,19 @@
1
+ :host {
2
+ --navbar-gap: var(--wa-space-m);
3
+ }
4
+
5
+ #bar {
6
+ display: flex;
7
+ justify-content: space-between;
8
+ align-items: center;
9
+ gap: var(--navbar-gap);
10
+ }
11
+
12
+ figure {
13
+ margin: 0;
14
+ }
15
+
16
+ slot[name=nav]::slotted(*) {
17
+ display: flex;
18
+ gap: var(--navbar-gap);
19
+ }
@@ -0,0 +1,20 @@
1
+ class Willamette::HeaderNavbar < Bridgetown::Component
2
+ def template
3
+ html -> { <<~HTML
4
+ #{html -> { dsd_style }}
5
+
6
+ <div id="bar">
7
+ <figure>
8
+ <slot name="logo"></slot>
9
+ </figure>
10
+
11
+ <slot name="search"></slot>
12
+
13
+ <nav>
14
+ <slot name="nav"></slot>
15
+ </nav>
16
+ </div>
17
+ HTML
18
+ }
19
+ end
20
+ end
@@ -0,0 +1,81 @@
1
+ :host {
2
+ --layout-padding: var(--wll-layout-padding);
3
+ --header-block-padding: var(--layout-padding) calc(var(--layout-padding) / 3);
4
+ --sidebar-start-min-inline-size: calc(var(--wa-space-scale) * 15rem);
5
+ --sidebar-start-max-inline-size: calc(var(--wa-space-scale) * 28rem);
6
+ --sidebar-end-min-inline-size: calc(var(--wa-space-scale) * 15rem);
7
+ --sidebar-end-max-inline-size: calc(var(--wa-space-scale) * 25rem);
8
+
9
+ display: grid;
10
+ grid-template: auto 1fr auto / auto minmax(100px, 1fr) auto;
11
+ }
12
+
13
+ slot {
14
+ display: block;
15
+
16
+ &::slotted(*) {
17
+ padding: var(--layout-padding);
18
+ }
19
+ }
20
+
21
+ slot[name=header] {
22
+ grid-column: 1 / 4;
23
+
24
+ &::slotted(*) {
25
+ padding-block: var(--header-block-padding);
26
+ }
27
+ }
28
+
29
+ slot[name=sidebar-start] {
30
+ grid-column: 1 / 2;
31
+
32
+ &::slotted(*) {
33
+ width: clamp(var(--sidebar-start-min-inline-size), 25vw, var(--sidebar-start-max-inline-size));
34
+ }
35
+ }
36
+
37
+ slot[name=content] {
38
+ grid-column: 2 / 3;
39
+ }
40
+
41
+ slot[name=sidebar-end] {
42
+ grid-column: 3 / 4;
43
+
44
+ &::slotted(*) {
45
+ width: clamp(var(--sidebar-end-min-inline-size), 22vw, var(--sidebar-end-max-inline-size));
46
+ }
47
+ }
48
+
49
+ slot[name=footer] {
50
+ grid-column: 1 / 4;
51
+ }
52
+
53
+ slot[name=skip-to-content] {
54
+ display: contents;
55
+ }
56
+
57
+ @media screen and (max-width: 1099px) {
58
+ slot[name=sidebar-end] {
59
+ position: relative;
60
+ }
61
+ }
62
+
63
+ @media screen and (max-width: 767px) {
64
+ :host {
65
+ grid-template-rows: auto auto 1fr auto;
66
+ }
67
+
68
+ slot[name=sidebar-start] {
69
+ grid-column: 1 / 4;
70
+
71
+ &::slotted(*) {
72
+ width: 100%;
73
+ }
74
+ }
75
+
76
+ slot[name=sidebar-end] {
77
+ &::slotted(*) {
78
+ width: calc(var(--sidebar-end-min-inline-size) * 1.25);
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,17 @@
1
+ class Willamette::HolyGrailLayout < Bridgetown::Component
2
+ def template
3
+ # inspired by Una Kravets demo: https://codepen.io/una/pen/mdVbdBy
4
+
5
+ html -> { <<~HTML
6
+ #{html -> { dsd_style }}
7
+
8
+ <slot name="skip-to-content"></slot>
9
+ <slot name="header" part="header"></slot>
10
+ <slot name="sidebar-start" part="sidebar-start"></slot>
11
+ <slot name="content" part="content"></slot>
12
+ <slot name="sidebar-end" part="sidebar-end"></slot>
13
+ <slot name="footer" part="footer"></slot>
14
+ HTML
15
+ }
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ <ul class="pagination">
2
+ <li>
3
+ <% if paginator.previous_page %>
4
+ <a href="<%= paginator.previous_page_path %>"><wa-icon label="<%= t "labels.previous" %>" name="circle-chevron-left"></wa-icon> <%= t "content.posts.newer_posts" %></a>
5
+ <% end %>
6
+ </li>
7
+ <li>
8
+ <% if paginator.next_page %>
9
+ <a href="<%= paginator.next_page_path %>"><%= t "content.posts.more_posts" %> <wa-icon label="<%= t "labels.next" %>" name="circle-chevron-right"></wa-icon></a>
10
+ <% end %>
11
+ </li>
12
+ </ul>
@@ -0,0 +1,7 @@
1
+ class Willamette::Pagination < Bridgetown::Component
2
+ attr_reader :paginator
3
+
4
+ def initialize(paginator:)
5
+ @paginator = paginator
6
+ end
7
+ end
@@ -0,0 +1,102 @@
1
+ wll-post-item {
2
+ &[headline-only] {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ gap: var(--wa-space-s);
6
+
7
+ > time {
8
+ min-width: 5ch;
9
+ text-align: right;
10
+ font-size: var(--wa-font-size-s);
11
+ margin-block-start: 0.175em;
12
+ }
13
+ }
14
+
15
+ &:not([headline-only]) {
16
+ display: block;
17
+ overflow: hidden;
18
+ border: solid 2px var(--wa-color-brand-border-quiet);
19
+ border-radius: var(--wa-border-radius-l);
20
+ corner-shape: squircle;
21
+ padding: 1rem;
22
+ height: 100%;
23
+ position: relative;
24
+
25
+ &::after {
26
+ box-shadow: inset 0px 0px 1.25rem color-mix(in srgb, var(--wll-color-glow-shadow), transparent 50%);
27
+ bottom: 0;
28
+ content: "";
29
+ display: block;
30
+ left: 0;
31
+ position: absolute;
32
+ right: 0;
33
+ top: 0;
34
+ height: 100%;
35
+ width: 100%;
36
+ }
37
+ }
38
+
39
+ figure {
40
+ margin-block-start: -1rem;
41
+ margin-inline-start: -1rem;
42
+ margin-block-end: 1.5rem;
43
+ width: calc(100% + 2rem);
44
+ border-radius: 0;
45
+ }
46
+
47
+ img {
48
+ display: block;
49
+ aspect-ratio: 5 / 3;
50
+ object-fit: cover;
51
+ object-position: center top;
52
+ border-radius: 0;
53
+
54
+ &[src^="data:"] {
55
+ width: 64px;
56
+ padding-block-start: calc(9.75vw - 32px);
57
+ padding-block-end: calc(9.75vw - 32px);
58
+ aspect-ratio: initial;
59
+ margin-inline: auto;
60
+ opacity: 0.3;
61
+
62
+ @media (max-width: 767px) {
63
+ padding-block-start: 18vw;
64
+ padding-block-end: 18vw;
65
+ }
66
+ }
67
+ }
68
+
69
+ h2 {
70
+ font-size: var(--wa-font-size-xl);
71
+
72
+ &:last-child {
73
+ margin-block-end: var(--wa-space-s);
74
+ }
75
+ }
76
+ }
77
+
78
+ .wa-dark wll-post-item:not([headline-only]) {
79
+ box-shadow: inset 0px 0px 1.5rem var(--wa-color-brand-10);
80
+
81
+ img[src^="data:"] {
82
+ filter: invert();
83
+ }
84
+ }
85
+
86
+ a:has(> wll-post-item) {
87
+ text-decoration: none;
88
+ }
89
+
90
+ a:has(> wll-post-item:not([headline-only])) {
91
+ color: inherit;
92
+
93
+ h2 { color: var(--wa-color-text-link) }
94
+ }
95
+
96
+ ul:has(li > a > wll-post-item) {
97
+ list-style-type: none;
98
+
99
+ > li {
100
+ margin-inline-start: 0;
101
+ }
102
+ }
@@ -0,0 +1,90 @@
1
+ class Willamette::PostItem < Bridgetown::Component
2
+ using Bridgetown::Refinements
3
+
4
+ def self.styles = [
5
+ :headline_only,
6
+ :headline_with_summary,
7
+ :headline_with_image,
8
+ :headline_with_image_and_summary,
9
+ ].freeze
10
+
11
+ def initialize(post:, post_style: :headline_only, heading_level: 2)
12
+ post_style = post_style.to_sym
13
+ unless post_style.within?(self.class.styles)
14
+ raise "Post style must be one of these options: #{self.class.styles.join(", ")}"
15
+ end
16
+
17
+ @post = post
18
+ @post_style = post_style
19
+ @heading_level = heading_level
20
+ end
21
+
22
+ def template = send(@post_style)
23
+
24
+ def headline_only
25
+ # TODO: what about title with html tags?
26
+ html -> { <<~HTML
27
+ <wll-post-item #{text->{__callee__.to_s.dasherize}}>
28
+ <strong>#{html->{@post.data.title}}</strong>
29
+ #{html->{timestamp}}
30
+ </wll-post-item>
31
+ HTML
32
+ }
33
+ end
34
+
35
+ def headline_with_summary
36
+ html -> { <<~HTML
37
+ <wll-post-item #{text->{__callee__.to_s.dasherize}}>
38
+ <hgroup>
39
+ #{html->{heading @post.data.title}}
40
+ <p>#{html->{summary}}</p>
41
+ </hgroup>
42
+ #{html->{timestamp}}
43
+ </wll-post-item>
44
+ HTML
45
+ }
46
+ end
47
+
48
+ def headline_with_image(show_summary: false) # rubocop:disable Metrics
49
+ image_path = @post.data.image.is_a?(String) ? @post.data.image : @post.data.image&.path
50
+ image_alt =
51
+ @post.data.image.is_a?(String) ?
52
+ t("content.featured_post_image") :
53
+ (@post.data.image&.alt || @post.data.image&.caption)
54
+ image_path ||= default_image_data
55
+ html -> { <<~HTML
56
+ <wll-post-item #{text->{__callee__.to_s.dasherize}}>
57
+ <hgroup>
58
+ <figure><img src="#{text->{image_path}}" alt="#{text->{image_alt}}" /></figure>
59
+ #{html->{heading @post.data.title}}
60
+ #{html(->{ show_summary ? <<~HTML
61
+ <p>#{html->{summary}}</p>
62
+ HTML
63
+ : "" })}
64
+ </hgroup>
65
+ #{html->{timestamp}}
66
+ </wll-post-item>
67
+ HTML
68
+ }
69
+ end
70
+
71
+ def headline_with_image_and_summary = headline_with_image(show_summary: true)
72
+
73
+ private
74
+
75
+ def default_image_data
76
+ <<~URL.strip
77
+ data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20640%20640%22%3E%3Cpath%20d%3D%22M160%20144C151.2%20144%20144%20151.2%20144%20160L144%20480C144%20488.8%20151.2%20496%20160%20496L480%20496C488.8%20496%20496%20488.8%20496%20480L496%20160C496%20151.2%20488.8%20144%20480%20144L160%20144zM96%20160C96%20124.7%20124.7%2096%20160%2096L480%2096C515.3%2096%20544%20124.7%20544%20160L544%20480C544%20515.3%20515.3%20544%20480%20544L160%20544C124.7%20544%2096%20515.3%2096%20480L96%20160zM224%20192C241.7%20192%20256%20206.3%20256%20224C256%20241.7%20241.7%20256%20224%20256C206.3%20256%20192%20241.7%20192%20224C192%20206.3%20206.3%20192%20224%20192zM360%20264C368.5%20264%20376.4%20268.5%20380.7%20275.8L460.7%20411.8C465.1%20419.2%20465.1%20428.4%20460.8%20435.9C456.5%20443.4%20448.6%20448%20440%20448L200%20448C191.1%20448%20182.8%20443%20178.7%20435.1C174.6%20427.2%20175.2%20417.6%20180.3%20410.3L236.3%20330.3C240.8%20323.9%20248.1%20320.1%20256%20320.1C263.9%20320.1%20271.2%20323.9%20275.7%20330.3L292.9%20354.9L339.4%20275.9C343.7%20268.6%20351.6%20264.1%20360.1%20264.1z%22%2F%3E%3C%2Fsvg%3E
78
+ URL
79
+ end
80
+
81
+ def heading(contents)
82
+ "<h#{@heading_level}>#{text contents, ->{ strip_html | smartify }}</h#{@heading_level}>"
83
+ end
84
+
85
+ def summary = @post.data.subtitle || @post.data.description || @post.summary
86
+
87
+ def timestamp(date = @post.data.date)
88
+ "<time>#{text date.to_date, -> { l format: :short }}</time>"
89
+ end
90
+ end
@@ -0,0 +1,12 @@
1
+ <ul class="pagination">
2
+ <li>
3
+ <% if resource.previous_resource %>
4
+ <a href="<%= resource.previous_resource.relative_url %>"><wa-icon label="<%= t "labels.previous" %>" name="circle-chevron-left"></wa-icon> <%= resource.previous_resource.data.title %></a>
5
+ <% end %>
6
+ </li>
7
+ <li>
8
+ <% if resource.next_resource %>
9
+ <a href="<%= resource.next_resource.relative_url %>"><%= resource.next_resource.data.title %> <wa-icon label="<%= t "labels.next" %>" name="circle-chevron-right"></wa-icon></a>
10
+ <% end %>
11
+ </li>
12
+ </ul>
@@ -0,0 +1,7 @@
1
+ class Willamette::PreviousNext < Bridgetown::Component
2
+ attr_reader :resource
3
+
4
+ def initialize(resource:)
5
+ @resource = resource
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ class Willamette::SearchDialog < Bridgetown::Component
2
+ def template
3
+ html -> { <<~HTML
4
+ <wll-search-dialog slot="footer">
5
+ <wa-dialog id="search-dialog" label="#{text->{t "labels.search" }}" without-header light-dismiss>
6
+ <wll-dialog-inner>
7
+ </wll-dialog-inner>
8
+
9
+ <wa-button slot="footer" size="small" appearance="outlined" variant="brand" pill data-dialog="close">
10
+ <wa-icon name="circle-xmark" slot="start"></wa-icon>
11
+ #{text->{t "labels.close" }}
12
+ </wa-button>
13
+ </wa-dialog>
14
+ </wll-search-dialog>
15
+ HTML
16
+ }
17
+ end
18
+ end