hotdocs 0.5.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8706b8fce2f72f7e41a569acf86d3ea355d5f2cff74bf5fac2c01b28976299fe
4
- data.tar.gz: 26f97697fee7e3ca88906684110fa280c91367e536bf8f7662f6b3f51f91d5fb
3
+ metadata.gz: 9c490655dd79fd1f2d45609e7ac0635c33e53a41462b8ff174cef7f73a8d8cec
4
+ data.tar.gz: 8a0033fe08f447ba4c2868dc01c18dd421e58cb8f5bf5edf778e8d596bd6bae1
5
5
  SHA512:
6
- metadata.gz: b413cd8ce039eda22ddae6036b6a88cbbb0b7d5359a1d02115c84e42f3a1f5931b6212dbd3af65c9b406a20b20bb9e8196f31f79af621111e79243d8cbcb5485
7
- data.tar.gz: 1dc1f6ba760c321d2c927958c27561aba34c7866ebc4da2a8d3d2a684bd1d392119b8e75334f02d356471e7d3749fabb4cdaa63a9323dcae8fc166b2ede31578
6
+ metadata.gz: 6bd387b959fac0bb3e060c1218f3976d997892e6455baac1a55e3cc33ffe660abf8c08032ac3b95e171f43b88a1ac72a30545cb049edaa851840a9c8a64e43f8
7
+ data.tar.gz: 2eb3ec4d38427ec22b22841843e271feeebb943446070c3c8a3b68fe419f70657113da900350f2fb819bd131197b32682235fd3c80f70f4d187147d06c62dcfe
data/README.md CHANGED
@@ -22,8 +22,8 @@ HotDocs is a set of optimized Rails components & tools for writing docs:
22
22
  | Styled components you can customize | ✅ | ✅ | ✅ |
23
23
  | Markdown (with syntax highlight & themes) | ✅ | ✅ | ✅ |
24
24
  | Static export | ✅ | ✅ | ✅ |
25
- | Search | | 🔌 | 🔌 |
26
- | Light / Dark | 🔜 | 🔌 | ✅ |
25
+ | Search | | 🔌 | 🔌 |
26
+ | Light / Dark | ✅ | 🔌 | ✅ |
27
27
  | Open source | ✅ | ✅ | ✅ |
28
28
  | Free | ✅ | ✅ | ✅ |
29
29
 
@@ -1,4 +1,4 @@
1
- <svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1
+ <svg aria-hidden="true" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
2
2
  <path d="M90 328C243.479 290.664 328.011 197.596 359 91" stroke="#F1B869" stroke-width="120" stroke-linecap="round"/>
3
3
  <path d="M67 376C252.429 326.849 354.56 204.329 392 64" stroke="#E34752" stroke-width="120" stroke-linecap="round"/>
4
4
  <path d="M147 415C300.479 377.664 385.011 284.596 416 178" stroke="#F1B869" stroke-width="160" stroke-linecap="round"/>
@@ -0,0 +1,19 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.element.addEventListener("click", this._checkNextInput);
6
+ }
7
+
8
+ disconnect() {
9
+ this.element.removeEventListener("click", this._checkNextInput);
10
+ }
11
+
12
+ _checkNextInput = (event) => {
13
+ event.preventDefault()
14
+ const inputs = Array.from(this.element.querySelectorAll('input'))
15
+ const i = inputs.findIndex(input => input.checked)
16
+ const j = (i+1) % inputs.length
17
+ inputs[j].checked = true
18
+ }
19
+ }
@@ -78,7 +78,7 @@ export default class extends Controller {
78
78
  console.warn(
79
79
  [
80
80
  "The search data is not present in the HTML.",
81
- "If you are in development, run `bundle exec rails hotdocs:index`.",
81
+ "If you are in development, run `bundle exec rails hotdocs:lunr:index`.",
82
82
  "If you are in production, assets compilation should have taken care of it.",
83
83
  ].join(" ")
84
84
  );
@@ -14,9 +14,36 @@
14
14
  --link-color: #707070;
15
15
  --nav-height: 3.75rem;
16
16
  --text-color: #1c1e21;
17
+ color-scheme: light;
18
+ }
19
+
20
+ @media (prefers-color-scheme: dark) {
21
+ :root {
22
+ --background-color: #1b1b1d;
23
+ --code-background-color: #2b2b2b;
24
+ --code-border-color: #ffffff22;
25
+ --column-border-color: #535353;
26
+ --link-active-color: white;
27
+ --link-color: #909090;
28
+ --text-color: #e3e1de;
29
+ color-scheme: dark;
30
+ }
17
31
  }
18
32
 
19
- [data-theme=dark]:root {
33
+ :root:has(#scheme-light:checked) {
34
+ --background-color: white;
35
+ --code-background-color: #f6f6f6;
36
+ --code-border-color: #00000022;
37
+ --column-border-color: #dadada;
38
+ --content-padding-bottom: 2rem;
39
+ --link-active-color: black;
40
+ --link-color: #707070;
41
+ --nav-height: 3.75rem;
42
+ --text-color: #1c1e21;
43
+ color-scheme: light;
44
+ }
45
+
46
+ :root:has(#scheme-dark:checked) {
20
47
  --background-color: #1b1b1d;
21
48
  --code-background-color: #2b2b2b;
22
49
  --code-border-color: #ffffff22;
@@ -24,6 +51,7 @@
24
51
  --link-active-color: white;
25
52
  --link-color: #909090;
26
53
  --text-color: #e3e1de;
54
+ color-scheme: dark;
27
55
  }
28
56
 
29
57
  html {
@@ -54,12 +82,31 @@
54
82
  --nav-padding-horizontal: 1.5rem;
55
83
  --nav-padding-vertical: 0.5rem;
56
84
  --nav-shadow: 0 1px 2px 0 #0000001a;
85
+ --nav-scheme-icon-fill: var(--link-color);
86
+ --nav-title-color: #1c1e21;
87
+ --nav-toggle-color: rgb(28, 30, 33);
88
+ --sidenav-padding-horizontal: 0.75rem;
89
+ }
90
+
91
+ @media (prefers-color-scheme: dark) {
92
+ :root {
93
+ --nav-background-color: #242526;
94
+ --nav-title-color: #e3e1de;
95
+ --nav-toggle-color: rgb(227, 225, 222);
96
+ }
97
+ }
98
+
99
+ :root:has(#scheme-light:checked) {
100
+ --nav-background-color: white;
101
+ --nav-padding-horizontal: 1.5rem;
102
+ --nav-padding-vertical: 0.5rem;
103
+ --nav-shadow: 0 1px 2px 0 #0000001a;
57
104
  --nav-title-color: #1c1e21;
58
105
  --nav-toggle-color: rgb(28, 30, 33);
59
106
  --sidenav-padding-horizontal: 0.75rem;
60
107
  }
61
108
 
62
- [data-theme=dark]:root {
109
+ :root:has(#scheme-dark:checked) {
63
110
  --nav-background-color: #242526;
64
111
  --nav-title-color: #e3e1de;
65
112
  --nav-toggle-color: rgb(227, 225, 222);
@@ -81,7 +128,7 @@
81
128
  padding: var(--nav-padding-vertical) var(--nav-padding-horizontal);
82
129
  position: sticky;
83
130
  top: 0;
84
- z-index: 10;
131
+ z-index: 100;
85
132
  }
86
133
 
87
134
  .nav__section {
@@ -144,6 +191,49 @@
144
191
  }
145
192
  }
146
193
 
194
+ .scheme {
195
+ display: flex;
196
+ height: 1.3rem;
197
+ position: relative;
198
+ width: 1.3rem;
199
+ }
200
+
201
+ .scheme__input {
202
+ -moz-appearance:none;
203
+ -webkit-appearance:none;
204
+ appearance:none;
205
+ background-color: var(--nav-background-color);
206
+ bottom: 0;
207
+ left: 0;
208
+ position: absolute;
209
+ right: 0;
210
+ top: 0;
211
+ }
212
+
213
+ .scheme__icon {
214
+ bottom: 0;
215
+ cursor: pointer;
216
+ display: none;
217
+ fill: var(--nav-scheme-icon-fill);
218
+ left: 0;
219
+ position: absolute;
220
+ right: 0;
221
+ top: 0;
222
+ z-index: 10;
223
+ }
224
+
225
+ #scheme-light:checked ~ .scheme__icon--light {
226
+ display: initial;
227
+ }
228
+
229
+ #scheme-dark:checked ~ .scheme__icon--dark {
230
+ display: initial;
231
+ }
232
+
233
+ #scheme-auto:checked ~ .scheme__icon--auto {
234
+ display: initial;
235
+ }
236
+
147
237
  .sidenav-backdrop {
148
238
  background-color: rgba(0, 0, 0, .6);
149
239
  inset: 0;
@@ -174,6 +264,7 @@
174
264
  transition-timing-function: ease-in-out;
175
265
  visibility: hidden;
176
266
  width: 85vw;
267
+ z-index: 100;
177
268
  }
178
269
 
179
270
  .sidenav--open .sidenav {
@@ -192,7 +283,6 @@
192
283
  padding: var(--nav-padding-vertical) var(--nav-padding-horizontal);
193
284
  position: sticky;
194
285
  top: 0;
195
- z-index: 10;
196
286
  }
197
287
 
198
288
  .sidenav__toggle {
@@ -233,7 +323,23 @@
233
323
  --search-text-color: var(--text-color);
234
324
  }
235
325
 
236
- [data-theme=dark]:root {
326
+ @media (prefers-color-scheme: dark) {
327
+ :root {
328
+ --search-background-color: #242526;
329
+ --search-button-background-color: #1b1b1b;
330
+ --search-excerpt-background-color: #1b1b1b;
331
+ --search-excerpt-border-color: #535353;
332
+ }
333
+ }
334
+
335
+ :root:has(#scheme-light:checked) {
336
+ --search-background-color: #f5f6f7;
337
+ --search-button-background-color: #e9e9e9;
338
+ --search-excerpt-background-color: white;
339
+ --search-excerpt-border-color: #d7d7d7;
340
+ }
341
+
342
+ :root:has(#scheme-dark:checked) {
237
343
  --search-background-color: #242526;
238
344
  --search-button-background-color: #1b1b1b;
239
345
  --search-excerpt-background-color: #1b1b1b;
@@ -248,6 +354,7 @@
248
354
  display: flex;
249
355
  gap: 0.5ch;
250
356
  padding: 0.5rem 0.5rem;
357
+ user-select: none;
251
358
 
252
359
  @media (min-width: 40rem) {
253
360
  padding: 0.25rem 0.5rem;
@@ -391,7 +498,19 @@
391
498
  --menu-link-color: var(--link-color);
392
499
  }
393
500
 
394
- [data-theme=dark]:root {
501
+ @media (prefers-color-scheme: dark) {
502
+ :root {
503
+ --menu-accordion-color: #a0a0a0;
504
+ --menu-link-hover-background-color: rgba(255, 255, 255, .05);
505
+ }
506
+ }
507
+
508
+ :root:has(#scheme-light:checked) {
509
+ --menu-accordion-color: #7f7f7f;
510
+ --menu-link-hover-background-color: rgba(0, 0, 0, .05);
511
+ }
512
+
513
+ :root:has(#scheme-dark:checked) {
395
514
  --menu-accordion-color: #a0a0a0;
396
515
  --menu-link-hover-background-color: rgba(255, 255, 255, .05);
397
516
  }
@@ -433,12 +552,14 @@
433
552
  }
434
553
 
435
554
  .menu__link {
555
+ align-items: center;
436
556
  border-radius: 0.25rem;
437
557
  color: var(--menu-link-color);
438
- display: inline-block;
558
+ display: flex;
439
559
  flex: 1 0 0;
560
+ height: 2.5rem;
440
561
  overflow: hidden;
441
- padding: 0.375rem 1rem;
562
+ padding-inline: 0.75rem;
442
563
  text-overflow: ellipsis;
443
564
  white-space: nowrap;
444
565
 
@@ -484,15 +605,20 @@
484
605
  display: flex;
485
606
  }
486
607
 
608
+ .menu__toggle-icon-wrapper {
609
+ display: flex;
610
+ justify-content: center;
611
+ width: 2.5rem;
612
+ }
613
+
487
614
  .menu__toggle-icon {
488
615
  color: var(--menu-accordion-color);
489
- transform: rotate(90deg);
490
- height: 2rem;
491
- width: 2rem;
616
+ height: 1rem;
617
+ width: 1rem;
492
618
  }
493
619
 
494
620
  .menu__row--expanded .menu__toggle-icon {
495
- transform: rotate(180deg);
621
+ transform: rotate(90deg);
496
622
  }
497
623
 
498
624
  /* CSS: CONTENT */
@@ -541,13 +667,30 @@
541
667
  --toc-link-color: #606060;
542
668
  }
543
669
 
544
- [data-theme=dark]:root {
670
+ @media (prefers-color-scheme: dark) {
671
+ :root {
672
+ --toc-background-color: #444444;
673
+ --toc-divider-color: #909090;
674
+ --toc-link-active-color: white;
675
+ --toc-link-color: #c0c0c0;
676
+ }
677
+ }
678
+
679
+ :root:has(#scheme-light:checked) {
680
+ --toc-background-color: #e9e9e9;
681
+ --toc-divider-color: #b0b0b0;
682
+ --toc-link-active-color: black;
683
+ --toc-link-color: #606060;
684
+ }
685
+
686
+ :root:has(#scheme-dark:checked) {
545
687
  --toc-background-color: #444444;
546
688
  --toc-divider-color: #909090;
547
689
  --toc-link-active-color: white;
548
690
  --toc-link-color: #c0c0c0;
549
691
  }
550
692
 
693
+
551
694
  .toc {
552
695
  background-color: var(--toc-background-color);
553
696
  border-radius: 5px;
@@ -583,7 +726,7 @@
583
726
  border-top: 1px solid var(--toc-divider-color);
584
727
  display: flex;
585
728
  flex-direction: column;
586
- font-size: 0.8rem;
729
+ font-size: 0.9rem;
587
730
  padding: 0.5rem 0.6rem;
588
731
  gap: 0.4rem;
589
732
  }
@@ -652,7 +795,7 @@
652
795
  border-left: 1px solid var(--column-border-color);
653
796
  display: flex;
654
797
  flex-direction: column;
655
- font-size: 0.8rem;
798
+ font-size: 0.9rem;
656
799
  gap: 0.4rem;
657
800
  padding-inline: 1.5rem;
658
801
  }
@@ -726,7 +869,17 @@
726
869
  --footer-background-color: #e9e9e9;
727
870
  }
728
871
 
729
- [data-theme=dark]:root {
872
+ @media (prefers-color-scheme: dark) {
873
+ :root {
874
+ --footer-background-color: #444444;
875
+ }
876
+ }
877
+
878
+ :root:has(#scheme-light:checked) {
879
+ --footer-background-color: #e9e9e9;
880
+ }
881
+
882
+ :root:has(#scheme-dark:checked) {
730
883
  --footer-background-color: #444444;
731
884
  }
732
885
 
@@ -872,7 +1025,19 @@
872
1025
  --announcement-color: white;
873
1026
  }
874
1027
 
875
- [data-theme=dark]:root {
1028
+ @media (prefers-color-scheme: dark) {
1029
+ :root {
1030
+ --announcement-background-color: white;
1031
+ --announcement-color: black;
1032
+ }
1033
+ }
1034
+
1035
+ :root:has(#scheme-light:checked) {
1036
+ --announcement-background-color: black;
1037
+ --announcement-color: white;
1038
+ }
1039
+
1040
+ :root:has(#scheme-dark:checked) {
876
1041
  --announcement-background-color: white;
877
1042
  --announcement-color: black;
878
1043
  }
@@ -29,8 +29,9 @@ module Hotdocs
29
29
  concat(content_tag(:span, name))
30
30
 
31
31
  concat(<<~SVG.gsub(/\n/, "").html_safe)
32
- <svg aria-hidden="true" viewBox="0 0 24 24" class="external-link__icon">
33
- <path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path>
32
+ <svg aria-hidden="true" class="external-link__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
33
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
34
+ <path fill="currentColor" d="M384 64C366.3 64 352 78.3 352 96C352 113.7 366.3 128 384 128L466.7 128L265.3 329.4C252.8 341.9 252.8 362.2 265.3 374.7C277.8 387.2 298.1 387.2 310.6 374.7L512 173.3L512 256C512 273.7 526.3 288 544 288C561.7 288 576 273.7 576 256L576 96C576 78.3 561.7 64 544 64L384 64zM144 160C99.8 160 64 195.8 64 240L64 496C64 540.2 99.8 576 144 576L400 576C444.2 576 480 540.2 480 496L480 416C480 398.3 465.7 384 448 384C430.3 384 416 398.3 416 416L416 496C416 504.8 408.8 512 400 512L144 512C135.2 512 128 504.8 128 496L128 240C128 231.2 135.2 224 144 224L224 224C241.7 224 256 209.7 256 192C256 174.3 241.7 160 224 160L144 160z"/>
34
35
  </svg>
35
36
  SVG
36
37
  end
@@ -62,8 +63,9 @@ module Hotdocs
62
63
 
63
64
  link_to(href, html_options) do
64
65
  concat(<<~SVG.html_safe)
65
- <svg aria-hidden="true" viewBox="0 0 40 40" class="edit-link__icon">
66
- <path fill="currentColor" d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path>
66
+ <svg aria-hidden="true" class="edit-link__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
67
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
68
+ <path fill="currentColor" d="M416.9 85.2L372 130.1L509.9 268L554.8 223.1C568.4 209.6 576 191.2 576 172C576 152.8 568.4 134.4 554.8 120.9L519.1 85.2C505.6 71.6 487.2 64 468 64C448.8 64 430.4 71.6 416.9 85.2zM338.1 164L122.9 379.1C112.2 389.8 104.4 403.2 100.3 417.8L64.9 545.6C62.6 553.9 64.9 562.9 71.1 569C77.3 575.1 86.2 577.5 94.5 575.2L222.3 539.7C236.9 535.6 250.2 527.9 261 517.1L476 301.9L338.1 164z"/>
67
69
  </svg>
68
70
  SVG
69
71
 
@@ -1,14 +1,30 @@
1
1
  <%# locals: (label:, url:, expanded: false) -%>
2
2
 
3
- <%= content_tag :div, class: "menu__row", data: { "controller": "accordion", "accordion-expanded-class-value": "menu__row--expanded", "accordion-expanded-value": expanded, "accordion-collapsed-aria-label-value": "Expand menu row '#{label}'", "accordion-expanded-aria-label-value": "Collapse menu row '#{label}'" } do %>
4
- <%= active_link_to label, url, class: "menu__link" if !url.nil? && !external_url?(url) %>
5
- <%= external_link_to label, url, class: "menu__link" if !url.nil? && external_url?(url) %>
3
+ <% if url.nil? %>
4
+ <%= content_tag :div, class: "menu__row", data: { "controller": "accordion", "accordion-expanded-class-value": "menu__row--expanded", "accordion-expanded-value": expanded, "accordion-collapsed-aria-label-value": "Expand menu row '#{label}'", "accordion-expanded-aria-label-value": "Collapse menu row '#{label}'" } do %>
5
+ <%= content_tag :button, type: "button", class: "menu__toggle menu__toggle--full", data: { action: "click->accordion#toggle", "accordion-target": "toggle" } do %>
6
+ <%= content_tag :span, label, class: "menu__link" %>
6
7
 
7
- <%= content_tag :button, type: "button", class: class_names("menu__toggle", "menu__toggle--full": url.nil?), data: { action: "click->accordion#toggle", "accordion-target": "toggle" } do %>
8
- <%= content_tag :span, label, class: "menu__link" if url.nil? %>
8
+ <div class="menu__toggle-icon-wrapper">
9
+ <svg aria-hidden="true" class="menu__toggle-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
10
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
11
+ <path fill="currentColor" d="M471.1 297.4C483.6 309.9 483.6 330.2 471.1 342.7L279.1 534.7C266.6 547.2 246.3 547.2 233.8 534.7C221.3 522.2 221.3 501.9 233.8 489.4L403.2 320L233.9 150.6C221.4 138.1 221.4 117.8 233.9 105.3C246.4 92.8 266.7 92.8 279.2 105.3L471.2 297.3z"/>
12
+ </svg>
13
+ </div>
14
+ <% end %>
15
+ <% end %>
16
+ <% else %>
17
+ <%= content_tag :div, class: "menu__row", data: { "controller": "accordion", "accordion-expanded-class-value": "menu__row--expanded", "accordion-expanded-value": expanded, "accordion-collapsed-aria-label-value": "Expand menu row '#{label}'", "accordion-expanded-aria-label-value": "Collapse menu row '#{label}'" } do %>
18
+ <%= active_link_to label, url, class: "menu__link" unless external_url?(url) %>
19
+ <%= external_link_to label, url, class: "menu__link" if external_url?(url) %>
9
20
 
10
- <svg class="menu__toggle-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
11
- <path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path>
12
- </svg>
21
+ <%= content_tag :button, type: "button", class: "menu__toggle", data: { action: "click->accordion#toggle", "accordion-target": "toggle" } do %>
22
+ <div class="menu__toggle-icon-wrapper">
23
+ <svg aria-hidden="true" class="menu__toggle-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
24
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
25
+ <path fill="currentColor" d="M471.1 297.4C483.6 309.9 483.6 330.2 471.1 342.7L279.1 534.7C266.6 547.2 246.3 547.2 233.8 534.7C221.3 522.2 221.3 501.9 233.8 489.4L403.2 320L233.9 150.6C221.4 138.1 221.4 117.8 233.9 105.3C246.4 92.8 266.7 92.8 279.2 105.3L471.2 297.3z"/>
26
+ </svg>
27
+ </div>
28
+ <% end %>
13
29
  <% end %>
14
30
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html data-theme="light" lang="<%= I18n.locale %>">
2
+ <html lang="<%= I18n.locale %>">
3
3
  <head>
4
4
  <title><%= content_for(:title) %></title>
5
5
 
@@ -8,39 +8,7 @@
8
8
  <%= stylesheet_link_tag "hotdocs/application", media: "all", "data-turbo-track": "reload" %>
9
9
  </head>
10
10
 
11
- <body data-controller="search">
12
- <dialog data-search-target="search" class="reset search">
13
- <div data-search-target="dialog" class="search__dialog">
14
- <div class="search__header">
15
- <input autofocus data-action="input->search#search" type="text" class="search__input"></input>
16
- <button aria-label="Close search dialog" type="button" class="search__dismiss" data-action="click->search#close">
17
- <svg viewBox="0 0 15 15">
18
- <g stroke="currentColor" stroke-width="1.2">
19
- <path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"></path>
20
- </g>
21
- </svg>
22
- </button>
23
- </div>
24
-
25
- <template data-search-target="resultTemplate">
26
- <li class="search__result">
27
- <h1></h1>
28
- <a href="#" class="search__result-excerpt"></a>
29
- </li>
30
- </template>
31
-
32
- <ul data-search-target="results">
33
- <li class="search__result search__result--loading">
34
- Loading index...
35
- </li>
36
- </ul>
37
- </div>
38
-
39
- <script type="application/json" data-search-target="data">
40
- <%= raw(Rails.application.assets.resolver.read("search_data.json")&.force_encoding("UTF-8") || [].to_json) %>
41
- </script>
42
- </dialog>
43
-
11
+ <body>
44
12
  <% if content_for?(:announcement) %>
45
13
  <%= content_for(:announcement) %>
46
14
  <% end %>
@@ -52,8 +20,9 @@
52
20
  <nav class="reset nav" data-controller="sidenav" data-sidenav-open-class-value="sidenav--open" data-sidenav-main-menu-class-value="sidenav__sections--main">
53
21
  <div class="nav__section">
54
22
  <button class="nav__toggle" type="button" aria-label="Toggle navigation" aria-expanded="false" data-action="click->sidenav#open">
55
- <svg viewBox="0 0 30 30" aria-hidden="true">
56
- <path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path>
23
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
24
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
25
+ <path fill="currentColor" d="M96 160C96 142.3 110.3 128 128 128L512 128C529.7 128 544 142.3 544 160C544 177.7 529.7 192 512 192L128 192C110.3 192 96 177.7 96 160zM96 320C96 302.3 110.3 288 128 288L512 288C529.7 288 544 302.3 544 320C544 337.7 529.7 352 512 352L128 352C110.3 352 96 337.7 96 320zM544 480C544 497.7 529.7 512 512 512L128 512C110.3 512 96 497.7 96 480C96 462.3 110.3 448 128 448L512 448C529.7 448 544 462.3 544 480z"/>
57
26
  </svg>
58
27
  </button>
59
28
 
@@ -81,14 +50,65 @@
81
50
  <% end %>
82
51
  </div>
83
52
 
84
- <div class="search-box">
85
- <button type="button" data-action="click->search#open:stop" class="search-box__button" aria-label="Open search dialog">
86
- <svg class="search-box__icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
87
- <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
88
- </svg>
53
+ <form class="scheme" data-controller="input-cycler" data-turbo-permanent id="scheme">
54
+ <input class="scheme__input" type="radio" name="scheme" id="scheme-auto" checked />
55
+ <label class="scheme__icon scheme__icon--auto" role="button" for="scheme-auto" title="System color scheme">
56
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M512 320C512 214 426 128 320 128L320 512C426 512 512 426 512 320zM64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576C178.6 576 64 461.4 64 320z"/></svg>
57
+ </label>
58
+
59
+ <input class="scheme__input" type="radio" name="scheme" id="scheme-light" />
60
+ <label class="scheme__icon scheme__icon--light" for="scheme-light" title="Light color scheme">
61
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M210.2 53.9C217.6 50.8 226 51.7 232.7 56.1L320.5 114.3L408.3 56.1C415 51.7 423.4 50.9 430.8 53.9C438.2 56.9 443.4 63.5 445 71.3L465.9 174.5L569.1 195.4C576.9 197 583.5 202.4 586.5 209.7C589.5 217 588.7 225.5 584.3 232.2L526.1 320L584.3 407.8C588.7 414.5 589.5 422.9 586.5 430.3C583.5 437.7 576.9 443.1 569.1 444.6L465.8 465.4L445 568.7C443.4 576.5 438 583.1 430.7 586.1C423.4 589.1 414.9 588.3 408.2 583.9L320.4 525.7L232.6 583.9C225.9 588.3 217.5 589.1 210.1 586.1C202.7 583.1 197.3 576.5 195.8 568.7L175 465.4L71.7 444.5C63.9 442.9 57.3 437.5 54.3 430.2C51.3 422.9 52.1 414.4 56.5 407.7L114.7 320L56.5 232.2C52.1 225.5 51.3 217.1 54.3 209.7C57.3 202.3 63.9 196.9 71.7 195.4L175 174.6L195.9 71.3C197.5 63.5 202.9 56.9 210.2 53.9zM239.6 320C239.6 275.6 275.6 239.6 320 239.6C364.4 239.6 400.4 275.6 400.4 320C400.4 364.4 364.4 400.4 320 400.4C275.6 400.4 239.6 364.4 239.6 320zM448.4 320C448.4 249.1 390.9 191.6 320 191.6C249.1 191.6 191.6 249.1 191.6 320C191.6 390.9 249.1 448.4 320 448.4C390.9 448.4 448.4 390.9 448.4 320z"/></svg>
62
+ </label>
63
+
64
+ <input class="scheme__input" type="radio" name="scheme" id="scheme-dark" />
65
+ <label class="scheme__icon scheme__icon--dark" for="scheme-dark" title="Dark color scheme">
66
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576C388.8 576 451.3 548.8 497.3 504.6C504.6 497.6 506.7 486.7 502.6 477.5C498.5 468.3 488.9 462.6 478.8 463.4C473.9 463.8 469 464 464 464C362.4 464 280 381.6 280 280C280 207.9 321.5 145.4 382.1 115.2C391.2 110.7 396.4 100.9 395.2 90.8C394 80.7 386.6 72.5 376.7 70.3C358.4 66.2 339.4 64 320 64z"/></svg>
67
+ </label>
68
+ </form>
69
+
70
+ <div class="search-box" data-controller="<%= search_provider %>">
71
+ <% if search_provider == "lunr" %>
72
+ <button type="button" data-action="click->lunr#open:stop" class="search-box__button" aria-label="Open search dialog">
73
+ <svg aria-hidden="true" class="search-box__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
74
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
75
+ <path fill="currentColor" d="M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 416C351.5 416 416 351.5 416 272C416 192.5 351.5 128 272 128C192.5 128 128 192.5 128 272C128 351.5 192.5 416 272 416z"/>
76
+ </svg>
77
+
78
+ <span class="search-box__label">Type / to search</span>
79
+ </button>
80
+
81
+ <dialog data-lunr-target="search" class="reset search">
82
+ <div data-lunr-target="dialog" class="search__dialog">
83
+ <div class="search__header">
84
+ <input autofocus data-action="input->lunr#search" type="text" class="search__input"></input>
85
+ <button aria-label="Close search dialog" type="button" class="search__dismiss" data-action="click->lunr#close">
86
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
87
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
88
+ <path fill="currentColor" d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"/>
89
+ </svg>
90
+ </button>
91
+ </div>
92
+
93
+ <template data-lunr-target="resultTemplate">
94
+ <li class="search__result">
95
+ <h1></h1>
96
+ <a href="#" class="search__result-excerpt"></a>
97
+ </li>
98
+ </template>
99
+
100
+ <ul data-lunr-target="results">
101
+ <li class="search__result search__result--loading">
102
+ Loading index...
103
+ </li>
104
+ </ul>
105
+ </div>
89
106
 
90
- <span class="search-box__label">Type / to search</span>
91
- </button>
107
+ <script type="application/json" data-lunr-target="data">
108
+ <%= raw(Rails.application.assets.resolver.read("lunr_data.json")&.force_encoding("UTF-8") || [].to_json) %>
109
+ </script>
110
+ </dialog>
111
+ <% end %>
92
112
  </div>
93
113
  </div>
94
114
 
@@ -106,10 +126,9 @@
106
126
  <% end %>
107
127
 
108
128
  <button aria-label="Close navigation" class="sidenav__toggle" type="button" data-action="click->sidenav#close">
109
- <svg viewBox="0 0 15 15">
110
- <g stroke="currentColor" stroke-width="1.2">
111
- <path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"></path>
112
- </g>
129
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
130
+ <!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
131
+ <path fill="currentColor" d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"/>
113
132
  </svg>
114
133
  </button>
115
134
  </div>
@@ -1,3 +1,3 @@
1
1
  module Hotdocs
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -157,6 +157,10 @@ create_file(Pathname(destination_root).join("app/helpers/hotdocs_helper.rb"), <<
157
157
  def fetcher_host
158
158
  "http://127.0.0.1:3000"
159
159
  end
160
+
161
+ def search_provider
162
+ "lunr"
163
+ end
160
164
  end
161
165
  FILE
162
166
 
@@ -169,7 +173,21 @@ create_file(Pathname(destination_root).join("app/assets/stylesheets/hotdocs/cust
169
173
  --docs-text-color: #1c1e21;
170
174
  }
171
175
 
172
- [data-theme=dark]:root {
176
+ @media (prefers-color-scheme: dark) {
177
+ :root {
178
+ --docs-code-background-color: #2b2b2b;
179
+ --docs-code-border-color: #ffffff22;
180
+ --docs-text-color: #e3e1de;
181
+ }
182
+ }
183
+
184
+ :root:has(#scheme-light:checked) {
185
+ --docs-code-background-color: #eee;
186
+ --docs-code-border-color: #00000022;
187
+ --docs-text-color: #1c1e21;
188
+ }
189
+
190
+ :root:has(#scheme-dark:checked) {
173
191
  --docs-code-background-color: #2b2b2b;
174
192
  --docs-code-border-color: #ffffff22;
175
193
  --docs-text-color: #e3e1de;
@@ -3,40 +3,45 @@ namespace :hotdocs do
3
3
  task :install do
4
4
  location = File.expand_path("../install/install.rb", __dir__)
5
5
  system("#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{location}")
6
- # Needed for hotdocs:index to find the generated ::HotdocsController
6
+ # Needed for hotdocs:lunr:index to find the generated ::HotdocsController
7
7
  Rails.application.reloader.reload!
8
- Rake::Task["hotdocs:index"].invoke
8
+ Rake::Task["hotdocs:lunr:index"].invoke
9
9
  end
10
10
 
11
- desc "Build search data"
12
- task index: :environment do
13
- path = Rails.root.join("app/assets/builds/search_data.json")
14
- # Propshaft caches the `@load_path`s. Rendering data goes through Propshaft
15
- # because of the assets, so the file must exist before rendering.
16
- File.write(path, "")
17
- data = render_search_data.call.to_json
18
- File.write(path, data)
11
+ namespace :lunr do
12
+ desc "Prepare lunr data"
13
+ task index: :environment do
14
+ helper = Object.new.extend(Hotdocs::ApplicationHelper)
15
+ next if helper.search_provider != "lunr"
16
+
17
+ path = Rails.root.join("app/assets/builds/lunr_data.json")
18
+ # Propshaft caches the `@load_path`s. Rendering data goes through Propshaft
19
+ # because of the assets, so the file must exist before rendering.
20
+ File.write(path, "")
21
+ data = render_lunr_data.call.to_json
22
+ File.write(path, data)
23
+ end
19
24
  end
20
25
  end
21
26
 
22
27
  if Rake::Task.task_defined?("assets:precompile")
23
- Rake::Task["assets:precompile"].enhance([ "hotdocs:index" ])
28
+ Rake::Task["assets:precompile"].enhance([ "hotdocs:lunr:index" ])
24
29
  end
25
30
 
26
31
  if Rake::Task.task_defined?("test:prepare")
27
- Rake::Task["test:prepare"].enhance([ "hotdocs:index" ])
32
+ Rake::Task["test:prepare"].enhance([ "hotdocs:lunr:index" ])
28
33
  elsif Rake::Task.task_defined?("spec:prepare")
29
- Rake::Task["spec:prepare"].enhance([ "hotdocs:index" ])
34
+ Rake::Task["spec:prepare"].enhance([ "hotdocs:lunr:index" ])
30
35
  elsif Rake::Task.task_defined?("db:test:prepare")
31
- Rake::Task["db:test:prepare"].enhance([ "hotdocs:index" ])
36
+ Rake::Task["db:test:prepare"].enhance([ "hotdocs:lunr:index" ])
32
37
  end
33
38
 
34
- def render_search_data
39
+ def render_lunr_data
35
40
  renderer = Class.new(::HotdocsController) do
36
41
  include Hotdocs::ApplicationHelper
37
42
 
38
43
  def call
39
- with_no_view_annotations { render_search_data }
44
+ with_no_view_annotations { render_lunr_data }
40
45
  end
41
46
 
42
47
  private
@@ -49,7 +54,7 @@ def render_search_data
49
54
  Rails.application.config.action_view.annotate_rendered_view_with_filename = annotate
50
55
  end
51
56
 
52
- def render_search_data
57
+ def render_lunr_data
53
58
  pages = pages_from(menu_items)
54
59
  $stderr.puts "Indexing #{pages.size} pages:"
55
60
  render_pages(pages).tap { $stderr.puts }
@@ -66,12 +71,12 @@ def render_search_data
66
71
 
67
72
  def pages_from(menu_items, parent = "Docs")
68
73
  menu_items
69
- .filter { _1.fetch(:url).start_with?("/") }
70
74
  .flat_map do |item|
71
- current = { title: item.fetch(:label), parent: parent, url: item.fetch(:url) }
75
+ current = { title: item.fetch(:label), parent: parent, url: item.fetch(:url, "") }
72
76
  children = pages_from(item.fetch(:children, []), item.fetch(:label))
73
77
  [ current ] + children
74
78
  end
79
+ .filter { _1.fetch(:url).start_with?("/") }
75
80
  end
76
81
 
77
82
  def render_path(path)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotdocs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 3v0k4
@@ -79,7 +79,8 @@ files:
79
79
  - app/assets/images/hotdocs/icon.svg
80
80
  - app/assets/javascript/controllers/accordion_controller.js
81
81
  - app/assets/javascript/controllers/fetcher_controller.js
82
- - app/assets/javascript/controllers/search_controller.js
82
+ - app/assets/javascript/controllers/input_cycler_controller.js
83
+ - app/assets/javascript/controllers/lunr_controller.js
83
84
  - app/assets/javascript/controllers/sidenav_controller.js
84
85
  - app/assets/javascript/controllers/toc_controller.js
85
86
  - app/assets/stylesheets/hotdocs/application.css