rawfeed 0.3.0 → 1.0.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +25 -0
  3. data/.gitignore +19 -0
  4. data/.gitlab/ci/gitlab-pages.yml +51 -0
  5. data/.hidden +6 -0
  6. data/404.html +6 -0
  7. data/Gemfile +51 -0
  8. data/README.md +102 -26
  9. data/_config.yml +77 -0
  10. data/_data/generic.yml +29 -0
  11. data/_data/screen/blog.yml +46 -0
  12. data/_data/screen/contact.yml +31 -0
  13. data/_data/screen/donate.yml +45 -0
  14. data/_data/screen/footer.yml +17 -0
  15. data/_data/screen/head.yml +49 -0
  16. data/_data/screen/home.yml +24 -0
  17. data/_data/screen/maintenance.yml +8 -0
  18. data/_data/screen/navbar.yml +41 -0
  19. data/_data/screen/page.yml +12 -0
  20. data/_data/screen/pixels.yml +7 -0
  21. data/_data/screen/pub.yml +4 -0
  22. data/_data/{resume.yml → screen/resume.yml} +8 -8
  23. data/_data/screen/socials.yml +20 -0
  24. data/_includes/assigned +36 -0
  25. data/_includes/layout/blog_search.html +22 -15
  26. data/_includes/layout/disqus.html +7 -8
  27. data/_includes/layout/footer.html +43 -28
  28. data/_includes/layout/giscus.html +15 -19
  29. data/_includes/layout/head.html +97 -75
  30. data/_includes/layout/maintenance.html +14 -11
  31. data/_includes/layout/navbar.html +253 -0
  32. data/_includes/layout/paginator.html +12 -13
  33. data/_includes/{alert → markdown/alert} +2 -2
  34. data/_includes/{chart → markdown/chart} +3 -3
  35. data/_includes/{image → markdown/image} +4 -4
  36. data/_includes/{socials → markdown/socials} +5 -5
  37. data/_includes/{video → markdown/video} +2 -2
  38. data/_layouts/blog/index.html +53 -0
  39. data/_layouts/{post.html → blog/post.html} +50 -36
  40. data/_layouts/blog/posts_by_tag.html +27 -0
  41. data/_layouts/blog/tags.html +32 -0
  42. data/_layouts/contact.html +156 -114
  43. data/_layouts/default.html +53 -48
  44. data/_layouts/donate.html +112 -0
  45. data/_layouts/error.html +9 -7
  46. data/_layouts/home.html +22 -19
  47. data/_layouts/licenses.html +2 -2
  48. data/_layouts/page.html +18 -17
  49. data/_layouts/pixels/index.html +75 -0
  50. data/_layouts/{pixel.html → pixels/post.html} +5 -5
  51. data/_layouts/pub.html +50 -44
  52. data/_layouts/resume.html +308 -265
  53. data/_pages/any-page.md +336 -0
  54. data/_pages/contact.md +16 -0
  55. data/_pages/donate.md +16 -0
  56. data/_pages/licenses.md +17 -0
  57. data/_pages/resume.md +14 -0
  58. data/_pixels/2025-10-15-first-my-pixel.md +34 -0
  59. data/_posts/2025-09-20-welcome-to-jekyll.md +36 -0
  60. data/_posts/2025-09-25-this-post-demonstrates-post-codeblocks.md +141 -0
  61. data/_posts/2025-09-25-this-post-demonstrates-post-content-styles.md +133 -0
  62. data/_posts/2025-10-04-content-styles-and-codeblocks.md +330 -0
  63. data/_sass/components/_markdown.scss +2 -2
  64. data/_sass/includes/_index.scss +2 -2
  65. data/_sass/includes/{_header.scss → _navbar.scss} +2 -6
  66. data/_sass/includes/_toc.scss +146 -0
  67. data/_sass/layouts/_blog.scss +1 -1
  68. data/_sass/layouts/_index.scss +2 -2
  69. data/_sass/layouts/_page.scss +0 -5
  70. data/_sass/layouts/_post.scss +8 -138
  71. data/_sass/layouts/{_tag-posts.scss → _posts_by_tag.scss} +3 -3
  72. data/_sass/layouts/_resume.scss +1 -1
  73. data/_sass/layouts/{_tag.scss → _tags.scss} +10 -6
  74. data/_sass/main.scss +2 -2
  75. data/_sass/theme/_dark.scss +2 -2
  76. data/_sass/theme/_light.scss +1 -1
  77. data/assets/js/blog.js +52 -18
  78. data/assets/js/contact.js +21 -20
  79. data/assets/js/default.js +67 -50
  80. data/assets/js/discus.js +2 -2
  81. data/assets/js/{donation.js → donate.js} +10 -9
  82. data/assets/js/home.js +18 -16
  83. data/assets/js/page.js +50 -172
  84. data/assets/js/toc.js +133 -0
  85. data/assets/json/blog_search.json +5 -7
  86. data/assets/vendors/fuse.min.js +9 -0
  87. data/blog/index.md +14 -0
  88. data/blog/tags/index.md +12 -0
  89. data/exe/rawfeed +29 -0
  90. data/index.md +15 -0
  91. data/lib/rawfeed/build/cleaner.rb +60 -0
  92. data/lib/rawfeed/build/image_minifier.rb +163 -0
  93. data/lib/rawfeed/build/minifier.rb +89 -0
  94. data/lib/rawfeed/build.rb +9 -0
  95. data/lib/rawfeed/command/backup.rb +124 -0
  96. data/lib/rawfeed/command/cli.rb +118 -0
  97. data/lib/rawfeed/command/installer.rb +156 -0
  98. data/lib/rawfeed/command/tools.rb +138 -0
  99. data/lib/rawfeed/{author.rb → content/author.rb} +7 -7
  100. data/lib/rawfeed/content/contact.rb +51 -0
  101. data/lib/rawfeed/content/donate.rb +48 -0
  102. data/lib/rawfeed/{draft.rb → content/draft.rb} +5 -3
  103. data/lib/rawfeed/content/licenses.rb +46 -0
  104. data/lib/rawfeed/{page.rb → content/page.rb} +4 -3
  105. data/lib/rawfeed/{pixel.rb → content/pixel.rb} +7 -4
  106. data/lib/rawfeed/content/post.rb +107 -0
  107. data/lib/rawfeed/{resume.rb → content/resume.rb} +23 -19
  108. data/lib/rawfeed/{layout.rb → core/layout.rb} +1 -1
  109. data/lib/rawfeed/core/utils.rb +103 -0
  110. data/lib/rawfeed/{version.rb → core/version.rb} +1 -1
  111. data/lib/rawfeed/{datelang.rb → plugin/datelang.rb} +5 -6
  112. data/lib/rawfeed/{pub.rb → plugin/pub.rb} +33 -31
  113. data/lib/rawfeed/{reading_time.rb → plugin/reading_time.rb} +5 -4
  114. data/lib/rawfeed/root.rb +3 -0
  115. data/lib/rawfeed.rb +36 -14
  116. data/pixels/index.md +11 -0
  117. data/robots.txt +26 -0
  118. metadata +153 -195
  119. data/_data/options.yml +0 -329
  120. data/_includes/layout/data.liquid +0 -31
  121. data/_includes/layout/header.html +0 -173
  122. data/_layouts/blog.html +0 -52
  123. data/_layouts/donation.html +0 -113
  124. data/_layouts/pixels.html +0 -71
  125. data/_layouts/tag.html +0 -33
  126. data/_layouts/tag_posts.html +0 -28
  127. data/assets/vendor/simple-jekyll-search.js +0 -433
  128. data/assets/vendor/simple-jekyll-search.min.js +0 -6
  129. data/lib/rawfeed/installer.rb +0 -37
  130. data/lib/rawfeed/post.rb +0 -60
  131. data/lib/rawfeed/utils.rb +0 -75
  132. /data/_includes/{details → markdown/details} +0 -0
  133. /data/_includes/{enddetails → markdown/enddetails} +0 -0
  134. /data/_includes/{endtabs → markdown/endtabs} +0 -0
  135. /data/_includes/{tabs → markdown/tabs} +0 -0
  136. /data/assets/images/{avatar_back.png → avatars/back.png} +0 -0
  137. /data/assets/images/{avatar_dark.png → avatars/dark.png} +0 -0
  138. /data/assets/images/{avatar_light.png → avatars/light.png} +0 -0
  139. /data/assets/images/{icons → donate}/bitcoin.svg +0 -0
  140. /data/assets/images/{icons → donate}/card.svg +0 -0
  141. /data/assets/images/{icons → donate}/donation.svg +0 -0
  142. /data/assets/images/{icons → donate}/lightning_network.svg +0 -0
  143. /data/assets/images/{icons → donate}/paypal.svg +0 -0
  144. /data/assets/images/{icons → donate}/pix.svg +0 -0
  145. /data/assets/images/{qrcode_btc_binance.jpg → donate/qrcode_btc_binance.jpg} +0 -0
  146. /data/assets/images/{qrcode_inter.jpg → donate/qrcode_inter.jpg} +0 -0
  147. /data/assets/images/{qrcode_wos.jpg → donate/qrcode_wos.jpg} +0 -0
  148. /data/assets/images/{icons → donate}/wos.png +0 -0
  149. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css +0 -0
  150. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css.map +0 -0
  151. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css +0 -0
  152. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css.map +0 -0
  153. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css +0 -0
  154. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css.map +0 -0
  155. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css +0 -0
  156. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css.map +0 -0
  157. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css +0 -0
  158. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css.map +0 -0
  159. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css +0 -0
  160. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css.map +0 -0
  161. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css +0 -0
  162. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css.map +0 -0
  163. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css +0 -0
  164. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css.map +0 -0
  165. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css +0 -0
  166. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css.map +0 -0
  167. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css +0 -0
  168. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css.map +0 -0
  169. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css +0 -0
  170. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css.map +0 -0
  171. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css +0 -0
  172. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css.map +0 -0
  173. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css +0 -0
  174. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css.map +0 -0
  175. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css +0 -0
  176. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css.map +0 -0
  177. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css +0 -0
  178. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css.map +0 -0
  179. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css +0 -0
  180. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css.map +0 -0
  181. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js +0 -0
  182. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js.map +0 -0
  183. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js +0 -0
  184. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js.map +0 -0
  185. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js +0 -0
  186. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js.map +0 -0
  187. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js +0 -0
  188. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js.map +0 -0
  189. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js +0 -0
  190. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js.map +0 -0
  191. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js +0 -0
  192. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js.map +0 -0
  193. /data/lib/rawfeed/{csp_filters.rb → plugin/csp_filters.rb} +0 -0
  194. /data/lib/rawfeed/{typescript_liquid.rb → plugin/typescript_liquid.rb} +0 -0
  195. /data/lib/rawfeed/{with_class.rb → plugin/with_class.rb} +0 -0
@@ -93,150 +93,20 @@
93
93
  margin: 40px 0px;
94
94
  font-size: 1.2rem;
95
95
  }
96
- }
97
-
98
- /* Automatic TOC */
99
- .auto-toc {
100
- position: fixed;
101
- left: 0;
102
- top: 15vh;
103
- height: auto;
104
- max-height: 70vh;
105
- width: 24px;
106
- background: transparent;
107
- z-index: 10000;
108
- transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease, box-shadow 0.3s ease;
109
- display: flex;
110
- align-items: center;
111
- overflow: hidden;
112
- // border-radius: 0 12px 12px 0;
113
- border: 2px solid transparent !important;
114
- padding: 20px 0;
115
-
116
- &:hover {
117
- width: 380px;
118
- background: var(--bg-color);
119
- box-shadow: 8px 0 25px rgba(0, 0, 0, 0.15);
120
- border: 2px dotted var(--background-focus-border-color) !important;
121
- border-left: none;
122
-
123
- .auto-toc-bars {
124
- opacity: 0;
125
- pointer-events: none;
126
- }
127
-
128
- .auto-toc-content {
129
- opacity: 1;
130
- transform: translateX(0);
131
- pointer-events: all;
132
- }
133
- }
134
96
 
135
- .auto-toc-bars {
136
- position: absolute;
137
- left: 6px;
138
- display: flex;
139
- flex-direction: column;
140
- gap: 4px;
141
- transition: opacity 0.2s ease;
142
-
143
- .toc-bar {
144
- width: 12px;
145
- height: 3px;
146
- background: var(--primary-color);
147
- border-radius: 2px;
148
- opacity: 0.8;
149
-
150
- &:nth-child(even) {
151
- width: 8px;
152
- }
153
- }
154
- }
155
-
156
- .auto-toc-content {
157
- width: 100%;
158
- height: 100%;
159
- max-height: 70vh;
160
- padding: 1.5rem;
161
- opacity: 0;
162
- transform: translateX(-20px);
163
- transition: opacity 0.3s ease, transform 0.3s ease;
164
- pointer-events: none;
165
- display: flex;
166
- flex-direction: column;
167
- background: var(--bg-color);
168
-
169
- h2 {
170
- font-size: 0.85rem;
171
- text-transform: uppercase;
172
- letter-spacing: 0.12em;
173
- color: var(--text-muted-color);
174
- margin-bottom: 1.2rem;
175
- font-weight: 800;
176
- border-bottom: 1px solid var(--border-color);
177
- padding-bottom: 0.5rem;
178
- }
179
-
180
- .auto-toc-list {
181
- flex: 1;
182
- overflow-y: auto;
97
+ &-tags {
98
+ .tag-list {
183
99
  list-style: none;
184
- padding: 0;
185
- margin: 0;
186
- padding-right: 10px;
187
-
188
- &::-webkit-scrollbar {
189
- width: 4px;
190
- }
100
+ display: inline-flex;
101
+ flex-wrap: wrap;
102
+ align-items: center;
103
+ gap: 4px;
191
104
 
192
- &::-webkit-scrollbar-thumb {
193
- background: var(--border-color);
194
- border-radius: 2px;
195
- }
196
-
197
- ul {
105
+ &__item {
198
106
  list-style: none;
199
- padding-left: 1rem;
200
- margin: 0.4rem 0;
201
- border-left: 1px solid var(--border-color);
202
- }
203
-
204
- li {
205
- margin: 0.5rem 0;
206
- font-size: 0.88rem;
207
-
208
- a {
209
- color: var(--text-muted-color);
210
- text-decoration: none;
211
- transition: all 0.2s ease;
212
- display: block;
213
- line-height: 1.4;
214
-
215
- &:hover {
216
- color: var(--primary-color);
217
- padding-left: 4px;
218
- }
219
-
220
- &.active {
221
- color: var(--primary-color);
222
- font-weight: 700;
223
- border-left: 3px solid var(--primary-color);
224
- padding-left: 12px;
225
- margin-left: 0;
226
-
227
- &::before {
228
- display: none;
229
- }
230
- }
231
- }
107
+ display: inline;
232
108
  }
233
109
  }
234
110
  }
235
111
  }
236
112
 
237
- /* Ensure post content doesn't get weird gaps if TOC is floating */
238
- @media (max-width: 1200px) {
239
- .auto-toc {
240
- display: none !important;
241
- }
242
- }
@@ -1,10 +1,10 @@
1
1
  @use "../components/index";
2
2
 
3
- .tag-posts {
3
+ .posts_by_tag {
4
4
  margin-top: -.1rem !important;
5
5
  margin-bottom: var(--margin-bottom);
6
6
 
7
- &-title {
7
+ &__title {
8
8
  font-size: 1.05rem !important;
9
9
  font-weight: bold;
10
10
  font-style: italic;
@@ -15,7 +15,7 @@
15
15
  }
16
16
  }
17
17
 
18
- &-list {
18
+ &__list {
19
19
  list-style: none !important;
20
20
  padding: 0 !important;
21
21
  margin: 0 !important;
@@ -258,7 +258,7 @@
258
258
  }
259
259
  }
260
260
 
261
- .sumary {
261
+ .summary {
262
262
  text-align: justify !important;
263
263
 
264
264
  }
@@ -1,6 +1,6 @@
1
1
  @use "../components/index";
2
2
 
3
- .tag {
3
+ .tags {
4
4
  // box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
5
5
  // border: 1px solid #ddd;
6
6
  // background-color: white;
@@ -8,13 +8,13 @@
8
8
  @extend %markdown;
9
9
  margin-bottom: var(--margin-bottom);
10
10
 
11
- &-title {
11
+ &__title {
12
12
  font-size: 1.05rem !important;
13
13
  font-weight: bold;
14
14
  margin: 0px 0px 30px 0px !important;
15
15
  }
16
16
 
17
- &-description {
17
+ &__description {
18
18
  margin-bottom: 40px;
19
19
  display: flex;
20
20
  align-items: center;
@@ -24,10 +24,14 @@
24
24
  }
25
25
  }
26
26
 
27
- &-list {
27
+ &__list {
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ align-items: center;
31
+ gap: 4px;
32
+
28
33
  &__item {
29
- display: inline-block;
30
- margin-bottom: 12px;
34
+ margin-bottom: 8px;
31
35
  @extend %tags-badge;
32
36
  }
33
37
  }
data/_sass/main.scss CHANGED
@@ -34,7 +34,7 @@ layout:
34
34
  --footer-border-color: #{theme.theme-color(footer-border-color, light)};
35
35
  --footer-bg-color: #{theme.theme-color(footer-bg-color, light)};
36
36
  --terminal-shadow: #{theme.theme-color(terminal-shadow, light)};
37
- --sumary-bg-color: #{theme.theme-color(sumary-bg-color, light)};
37
+ --summary-bg-color: #{theme.theme-color(summary-bg-color, light)};
38
38
  --checkbox-border-color: #{theme.theme-color(checkbox-border-color, light)};
39
39
  --checkbox-border-color-after: #{theme.theme-color(checkbox-border-color-after, light)};
40
40
  --table-border-color: #{theme.theme-color(table-border-color, light)};
@@ -107,7 +107,7 @@ layout:
107
107
  --footer-border-color: #{theme.theme-color(footer-border-color, dark)};
108
108
  --footer-bg-color: #{theme.theme-color(footer-bg-color, dark)};
109
109
  --terminal-shadow: #{theme.theme-color(terminal-shadow, dark)};
110
- --sumary-bg-color: #{theme.theme-color(sumary-bg-color, dark)};
110
+ --summary-bg-color: #{theme.theme-color(summary-bg-color, dark)};
111
111
  --checkbox-border-color: #{theme.theme-color(checkbox-border-color, dark)};
112
112
  --checkbox-border-color-after: #{theme.theme-color(checkbox-border-color-after, dark)};
113
113
  --table-color: #{theme.theme-color(table-color, dark)};
@@ -21,7 +21,7 @@ $theme-colors: (
21
21
  footer-border-color: #020202,
22
22
  footer-bg-color: #111111,
23
23
  terminal-shadow: 0 10px 30px rgba(0, 0, 0, 0.7),
24
- sumary-bg-color: #303030,
24
+ summary-bg-color: #303030,
25
25
  checkbox-border-color: #2b2b2b,
26
26
  checkbox-border-color-after: #999999,
27
27
  table-border-color: #a5a5a5,
@@ -90,5 +90,5 @@ $theme-colors: (
90
90
  // footer-border-color: #020202,
91
91
  // footer-bg-color: #111111,
92
92
  // terminal-shadow: 0 10px 30px rgba(0,0,0,0.7),
93
- // sumary-bg-color: #1f1f1f,
93
+ // summary-bg-color: #1f1f1f,
94
94
  // );
@@ -13,7 +13,7 @@ $theme-colors: (
13
13
  footer-border-color: #e4e4e4,
14
14
  footer-bg-color: #f3f3f3,
15
15
  terminal-shadow: 0 10px 30px rgba(0, 0, 0, 0.7),
16
- sumary-bg-color: #fbfbfb,
16
+ summary-bg-color: #fbfbfb,
17
17
  checkbox-border-color: #d3d3d3,
18
18
  checkbox-border-color-after: #1f1f1f,
19
19
  table-border-color: #a5a5a5,
data/assets/js/blog.js CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  ---
3
3
 
4
- {%- include layout/data.liquid -%}
4
+ {%- include assigned -%}
5
5
 
6
6
  document.addEventListener("DOMContentLoaded", () => {
7
7
  const btn = document.getElementById('blog-search__btn');
@@ -9,9 +9,9 @@ document.addEventListener("DOMContentLoaded", () => {
9
9
  const searchInput = document.getElementById('blog-search__input');
10
10
  const blogPosts = document.getElementById('posts');
11
11
  const searchResults = document.getElementById('blog-search__results');
12
- const searchResultsWrapper = document.getElementById('blog-search__results-wrapper');
12
+ const searchResultsWrap = document.getElementById('blog-search__results-wrap');
13
13
  const btnSearchClean = document.getElementById('blog-search__btn-clean');
14
- const blogSeachInput = document.getElementById('blog-search__input');
14
+ const blogSearchInput = document.getElementById('blog-search__input');
15
15
 
16
16
 
17
17
  if (!btn || !box) return;
@@ -27,7 +27,7 @@ document.addEventListener("DOMContentLoaded", () => {
27
27
  box.removeEventListener('transitionend', onOpened);
28
28
  }
29
29
  });
30
- blogSeachInput.focus();
30
+ blogSearchInput.focus();
31
31
  };
32
32
 
33
33
  const closeSearch = () => {
@@ -59,7 +59,7 @@ document.addEventListener("DOMContentLoaded", () => {
59
59
  closeSearch();
60
60
  searchInput.value = '';
61
61
  blogPosts.classList.remove('disabled');
62
- searchResultsWrapper.classList.add('disabled');
62
+ searchResultsWrap.classList.add('disabled');
63
63
  } else {
64
64
  openSearch();
65
65
  }
@@ -75,11 +75,11 @@ document.addEventListener("DOMContentLoaded", () => {
75
75
  --------------------------------------------------------------------------------------------------
76
76
  */
77
77
  function clearSearch() {
78
- blogSeachInput.value = '';
78
+ blogSearchInput.value = '';
79
79
  blogPosts.classList.remove('disabled');
80
80
  searchResults.classList.add('disabled');
81
- searchResultsWrapper.classList.add('disabled');
82
- blogSeachInput.focus();
81
+ searchResultsWrap.classList.add('disabled');
82
+ blogSearchInput.focus();
83
83
  }
84
84
  btnSearchClean.addEventListener('click', clearSearch);
85
85
  document.addEventListener('keydown', (e) => {
@@ -92,22 +92,56 @@ document.addEventListener("DOMContentLoaded", () => {
92
92
  /* open results and close posts in search (toggle)
93
93
  --------------------------------------------------------------------------------------------------
94
94
  */
95
+ /* Fuse.js search implementation
96
+ --------------------------------------------------------------------------------------------------
97
+ */
98
+ let fuse;
99
+ const searchJsonUrl = "{{ '/assets/json/blog_search.json' | relative_url }}";
100
+
101
+ fetch(searchJsonUrl)
102
+ .then(response => response.json())
103
+ .then(data => {
104
+ fuse = new Fuse(data, {
105
+ keys: ['title', 'tags'],
106
+ threshold: 0.3,
107
+ includeMatches: true
108
+ });
109
+ })
110
+ .catch(error => {
111
+ console.error('Error fetching search data:', error);
112
+ });
113
+
114
+ function renderResults(results) {
115
+ if (results.length === 0) {
116
+ searchResults.innerHTML = '<p>{{ blog.no_results | default: "No results found" }}</p>';
117
+ return;
118
+ }
119
+
120
+ const html = results.map(result => {
121
+ const item = result.item;
122
+ // Using existing template logic
123
+ return `<li><span class="blog-list__meta"><time datetime="${item.date}">${item.date}</time></span>&nbsp;»&nbsp; <a class="blog-list__link" href="{{ site.url }}${item.url}">${item.title}</a></li>`;
124
+ }).join('');
125
+
126
+ searchResults.innerHTML = html;
127
+ }
128
+
95
129
  searchInput.addEventListener('input', () => {
96
- if (searchInput.value.trim().length > 0) {
130
+ const query = searchInput.value.trim();
131
+ if (query.length > 0) {
97
132
  blogPosts.classList.add('disabled');
98
133
  searchResults.classList.remove('disabled');
99
- searchResultsWrapper.classList.remove('disabled');
134
+ searchResultsWrap.classList.remove('disabled');
135
+
136
+ if (fuse) {
137
+ const results = fuse.search(query);
138
+ renderResults(results);
139
+ }
100
140
  } else {
101
141
  blogPosts.classList.remove('disabled');
102
142
  searchResults.classList.add('disabled');
103
- searchResultsWrapper.classList.add('disabled');
143
+ searchResultsWrap.classList.add('disabled');
144
+ searchResults.innerHTML = '';
104
145
  }
105
146
  });
106
- var sjs = SimpleJekyllSearch({
107
- searchInput: document.getElementById('blog-search__input'),
108
- resultsContainer: document.getElementById('blog-search__results'),
109
- searchResultTemplate: '<li><span class="blog-list__meta"><time datetime="{date}">{date}</time></span>&nbsp;»&nbsp; <a class="blog-list__link" href="{{ site.url }}{url}">{title}</a></li>',
110
- noResultsText: '<p>{{ blog_.no_results | default: "No results found" }}</p>',
111
- json: "{{ '/assets/json/blog_search.json' | relative_url }}"
112
- })
113
147
  });
data/assets/js/contact.js CHANGED
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  ---
3
3
 
4
- {%- include layout/data.liquid -%}
4
+ {%- include assigned -%}
5
5
 
6
6
  document.addEventListener("DOMContentLoaded", () => {
7
7
  const form = document.getElementById("contactForm");
8
8
  if (form) {
9
9
  const submitButton = document.getElementById("submitButton");
10
- const endpoint = "{{ head_.google.apps_script.url }}"; // URL Google Apps Script
10
+ const endpoint = "{{ head.google.apps_script.url }}"; // URL Google Apps Script
11
11
 
12
12
  // get modal
13
13
  function showModal(title, message, type = 'success') {
14
14
  const modalEl = document.getElementById('contactMessageModal');
15
+ if (!modalEl) return;
15
16
  const modalTitle = modalEl.querySelector('.modal-title');
16
17
  const modalBody = modalEl.querySelector('.modal-body');
17
18
  const modalContent = modalEl.querySelector('.modal-content');
@@ -38,29 +39,29 @@ document.addEventListener("DOMContentLoaded", () => {
38
39
  form.addEventListener("submit", async (e) => {
39
40
  e.preventDefault();
40
41
 
41
- const recaptchaResponse = grecaptcha.getResponse();
42
- if (!recaptchaResponse) {
42
+ if (typeof grecaptcha === 'undefined' || !grecaptcha.getResponse()) {
43
43
  showModal(
44
- "{{ contact_.recaptcha.warning.title | default: 'Warning' }}",
45
- `{{ contact_.recaptcha.warning.content | default: "Please tick the 'I'm not a robot' box." }}`,
44
+ "{{ contact.recaptcha.warning.title | default: 'Warning' }}",
45
+ `{{ contact.recaptcha.warning.content | default: "Please tick the 'I'm not a robot' box." }}`,
46
46
  "warning"
47
47
  );
48
48
  return;
49
49
  }
50
50
 
51
51
  const textarea = document.getElementById('textMessage');
52
+ if (!textarea) return;
52
53
  const text = textarea.value.trim();
53
- if (text.length < "{{ contact_.message.caracters.min }}" ) {
54
+ if (text.length < "{{ contact.message.characters.min }}" ) {
54
55
  showModal(
55
- "{{ contact_.message.caracters.warning.title | default: 'Warning' }}",
56
- "{{ contact_.message.caracters.warning.content | default: 'The message must have at least 50 characters.' }}",
56
+ "{{ contact.message.characters.warning.title | default: 'Warning' }}",
57
+ "{{ contact.message.characters.warning.content | default: 'The message must have at least 50 characters.' }}",
57
58
  "warning"
58
59
  );
59
60
  return;
60
61
  }
61
62
 
62
63
  submitButton.disabled = true;
63
- submitButton.textContent = "{{ contact_.message.status | default: 'Sending...Wait' }}";
64
+ submitButton.textContent = "{{ contact.message.status | default: 'Sending...Wait' }}";
64
65
 
65
66
  const formData = new FormData(form);
66
67
  const data = Object.fromEntries(formData.entries());
@@ -80,31 +81,31 @@ document.addEventListener("DOMContentLoaded", () => {
80
81
  form.reset();
81
82
  grecaptcha.reset();
82
83
  showModal(
83
- "{{ contact_.message.success.title | default: 'Message Sent' }}",
84
- "{{ contact_.message.success.content | default: 'Your message has been sent successfully!' }}",
84
+ "{{ contact.message.success.title | default: 'Message Sent' }}",
85
+ "{{ contact.message.success.content | default: 'Your message has been sent successfully!' }}",
85
86
  "success"
86
87
  );
87
88
  } else {
88
89
  showModal(
89
- "{{ contact_.message.error.title | default: 'Error' }}",
90
- "{{ contact_.message.error.content | default: 'Something went wrong while sending your message.' }}",
90
+ "{{ contact.message.error.title | default: 'Error' }}",
91
+ "{{ contact.message.error.content | default: 'Something went wrong while sending your message.' }}",
91
92
  "error"
92
93
  );
93
- throw new Error(result.message || "{{ contact_.message.error.content | default: 'An unknown error has occurred.' }}");
94
+ throw new Error(result.message || "{{ contact.message.error.content | default: 'An unknown error has occurred.' }}");
94
95
  }
95
96
 
96
97
  } catch (error) {
97
98
  console.error("Error sending:", error);
98
99
  if (error.message.includes("reCAPTCHA")) {
99
100
  showModal(
100
- "{{ contact_.message.error.title | default: 'Error' }}",
101
- "{{ contact_.recaptcha.fail | default: 'Verification failed. Please reload the page and try again.' }}",
101
+ "{{ contact.message.error.title | default: 'Error' }}",
102
+ "{{ contact.recaptcha.fail | default: 'Verification failed. Please reload the page and try again.' }}",
102
103
  "error"
103
104
  );
104
105
  } else {
105
106
  showModal(
106
- "{{ contact_.message.error.title | default: 'Error' }}",
107
- "{{ contact_.recaptcha.error | default: 'An error occurred while sending the message. Please try again.' }}",
107
+ "{{ contact.message.error.title | default: 'Error' }}",
108
+ "{{ contact.recaptcha.error | default: 'An error occurred while sending the message. Please try again.' }}",
108
109
  "error"
109
110
  );
110
111
  }
@@ -112,7 +113,7 @@ document.addEventListener("DOMContentLoaded", () => {
112
113
 
113
114
  } finally {
114
115
  submitButton.disabled = false;
115
- submitButton.textContent = "{{ contact_.button.text | default: 'Send!' }}";
116
+ submitButton.textContent = "{{ contact.button.text | default: 'Send!' }}";
116
117
  }
117
118
  });
118
119
  }
data/assets/js/default.js CHANGED
@@ -14,7 +14,7 @@ document.addEventListener("DOMContentLoaded", () => {
14
14
  if (modalEl) {
15
15
  const flipperAvatars = document.querySelectorAll('.avatar-flipper__open-true');
16
16
  const modalAvatar = document.getElementById('modalAvatar');
17
- const header = document.querySelector('.header');
17
+ const navigation = document.querySelector('.navigation');
18
18
  const bsModal = new bootstrap.Modal(modalEl);
19
19
 
20
20
  flipperAvatars.forEach((flipper) => {
@@ -43,7 +43,7 @@ document.addEventListener("DOMContentLoaded", () => {
43
43
  modalAvatar.classList.remove("modal-avatar");
44
44
  void modalAvatar.offsetWidth;
45
45
  modalAvatar.classList.add("modal-avatar");
46
- header.classList.remove("modal-active");
46
+ navigation.classList.remove("modal-active");
47
47
 
48
48
  flipperAvatars.forEach((flipper) => flipper.classList.add("hidden"));
49
49
  });
@@ -60,18 +60,20 @@ document.addEventListener("DOMContentLoaded", () => {
60
60
  const topButton = document.getElementById("top-link");
61
61
  const scrollThreshold = 700;
62
62
 
63
- window.addEventListener("scroll", () => {
64
- if (window.scrollY > scrollThreshold) {
65
- topButton.classList.add("show");
66
- } else {
67
- topButton.classList.remove("show");
68
- }
69
- });
63
+ if (topButton) {
64
+ window.addEventListener("scroll", () => {
65
+ if (window.scrollY > scrollThreshold) {
66
+ topButton.classList.add("show");
67
+ } else {
68
+ topButton.classList.remove("show");
69
+ }
70
+ });
70
71
 
71
- topButton.addEventListener("click", (e) => {
72
- e.preventDefault();
73
- window.scrollTo({ top: 0, behavior: "smooth" });
74
- });
72
+ topButton.addEventListener("click", (e) => {
73
+ e.preventDefault();
74
+ window.scrollTo({ top: 0, behavior: "smooth" });
75
+ });
76
+ }
75
77
 
76
78
  /* function Giscus
77
79
  --------------------------------------------------------------------------------------------------
@@ -94,7 +96,7 @@ document.addEventListener("DOMContentLoaded", () => {
94
96
  // Let's use a timeout to ensure the Giscus iframe is ready
95
97
  const giscusInterval = setInterval(() => {
96
98
  const giscusFrame = document.querySelector('iframe.giscus-frame');
97
- // Se o iframe existir no documento...
99
+ // If the iframe exists in the document...
98
100
  if (giscusFrame) {
99
101
  // ...we sent the message...
100
102
  giscusFrame.contentWindow.postMessage(message, 'https://giscus.app');
@@ -115,39 +117,43 @@ document.addEventListener("DOMContentLoaded", () => {
115
117
  --------------------------------------------------------------------------------------------------
116
118
  */
117
119
  const toggleButton = document.getElementById('toggle-theme');
118
- const icon = toggleButton.querySelector('i');
119
- // const avatar = document.getElementById('avatarImage');
120
120
  const root = document.documentElement;
121
121
 
122
- function setTheme(theme) {
123
- root.setAttribute('data-theme', theme);
122
+ if (toggleButton) {
123
+ const icon = toggleButton.querySelector('i');
124
124
 
125
- icon.classList.remove('fa-sun', 'fa-moon');
126
- icon.classList.add(theme === 'dark' ? 'fa-sun' : 'fa-moon');
125
+ function setTheme(theme) {
126
+ root.setAttribute('data-theme', theme);
127
127
 
128
- if (typeof setGiscusTheme === 'function') {
129
- setGiscusTheme(theme);
130
- }
128
+ if (icon) {
129
+ icon.classList.remove('fa-sun', 'fa-moon');
130
+ icon.classList.add(theme === 'dark' ? 'fa-sun' : 'fa-moon');
131
+ }
131
132
 
132
- // if (avatar) {
133
- // avatar.src = theme === 'light'
134
- // ? avatar.dataset.light
135
- // : avatar.dataset.dark;
136
- // }
133
+ if (typeof setGiscusTheme === 'function') {
134
+ setGiscusTheme(theme);
135
+ }
137
136
 
138
- localStorage.setItem('theme', theme);
137
+ // if (avatar) {
138
+ // avatar.src = theme === 'light'
139
+ // ? avatar.dataset.light
140
+ // : avatar.dataset.dark;
141
+ // }
139
142
 
140
- }
143
+ localStorage.setItem('theme', theme);
141
144
 
142
- // boot with saved or light
143
- setTheme(localStorage.getItem('theme') || 'light');
145
+ }
144
146
 
145
- // change theme on click
146
- toggleButton.addEventListener('click', () => {
147
- const current = root.getAttribute('data-theme');
148
- const next = current === 'light' ? 'dark' : 'light';
149
- setTheme(next);
150
- });
147
+ // boot with saved or light
148
+ setTheme(localStorage.getItem('theme') || 'light');
149
+
150
+ // change theme on click
151
+ toggleButton.addEventListener('click', () => {
152
+ const current = root.getAttribute('data-theme');
153
+ const next = current === 'light' ? 'dark' : 'light';
154
+ setTheme(next);
155
+ });
156
+ }
151
157
 
152
158
 
153
159
  /* highlight code
@@ -174,19 +180,30 @@ document.addEventListener("DOMContentLoaded", () => {
174
180
  highlightBlock.parentNode.insertBefore(container, highlightBlock);
175
181
  container.appendChild(header);
176
182
  container.appendChild(highlightBlock);
183
+ });
177
184
 
178
- // event copy
179
- button.addEventListener("click", () => {
180
- const codeElement = highlightBlock.querySelector("td.code");
181
- const textToCopy = codeElement ? codeElement.innerText.trim() : highlightBlock.innerText.trim();
182
-
183
- navigator.clipboard.writeText(textToCopy).then(() => {
184
- icon.className = "fa-solid fa-check";
185
- setTimeout(() => (icon.className = "fa-solid fa-clipboard"), 2000);
186
- }, () => {
187
- icon.className = "fa-solid fa-xmark";
188
- setTimeout(() => (icon.className = "fa-solid fa-clipboard"), 2000);
189
- });
185
+ // event delegation for copy buttons (works even on cloned nodes inside <details>)
186
+ document.addEventListener("click", (e) => {
187
+ const btn = e.target.closest(".copy-btn");
188
+ if (!btn) return;
189
+
190
+ const container = btn.closest(".code-block-container");
191
+ if (!container) return;
192
+
193
+ const highlightBlock = container.querySelector(".highlight, figure.highlight");
194
+ if (!highlightBlock) return;
195
+
196
+ const codeElement = highlightBlock.querySelector("td.code");
197
+ const textToCopy = codeElement ? codeElement.innerText.trim() : highlightBlock.innerText.trim();
198
+
199
+ const icon = btn.querySelector("i");
200
+
201
+ navigator.clipboard.writeText(textToCopy).then(() => {
202
+ icon.className = "fa-solid fa-check";
203
+ setTimeout(() => (icon.className = "fa-solid fa-clipboard"), 2000);
204
+ }, () => {
205
+ icon.className = "fa-solid fa-xmark";
206
+ setTimeout(() => (icon.className = "fa-solid fa-clipboard"), 2000);
190
207
  });
191
208
  });
192
209