jekyll-theme-zer0 0.15.0 → 0.16.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.
data/_data/ui-text.yml ADDED
@@ -0,0 +1,321 @@
1
+ # User interface text and labels
2
+ # Used by: Various includes and layouts throughout the theme
3
+ # Purpose: Multilingual UI strings for internationalization (i18n)
4
+
5
+ # English (default)
6
+ # -----------------
7
+ en: &DEFAULT_EN
8
+ skip_links : "Skip links"
9
+ skip_primary_nav : "Skip to primary navigation"
10
+ skip_content : "Skip to content"
11
+ skip_footer : "Skip to footer"
12
+ page : "Page"
13
+ pagination_previous : "Previous"
14
+ pagination_next : "Next"
15
+ breadcrumb_home_label : "Home"
16
+ breadcrumb_separator : "/"
17
+ menu_label : "Toggle menu"
18
+ search_label : "Toggle search"
19
+ toc_label : "On this page"
20
+ ext_link_label : "Direct link"
21
+ less_than : "less than"
22
+ minute_read : "minute read"
23
+ share_on_label : "Share on"
24
+ meta_label :
25
+ tags_label : "Tags:"
26
+ categories_label : "Categories:"
27
+ date_label : "Updated:"
28
+ author_label : "Author:"
29
+ comments_label : "Leave a comment"
30
+ comments_title : "Comments"
31
+ more_label : "Learn more"
32
+ related_label : "You may also enjoy"
33
+ follow_label : "Follow:"
34
+ feed_label : "Feed"
35
+ powered_by : "Powered by"
36
+ website_label : "Website"
37
+ email_label : "Email"
38
+ recent_posts : "Recent posts"
39
+ undefined_wpm : "Undefined parameter words_per_minute at _config.yml"
40
+ comment_form_info : "Your email address will not be published. Required fields are marked"
41
+ comment_form_comment_label : "Comment"
42
+ comment_form_md_info : "Markdown is supported."
43
+ comment_form_name_label : "Name"
44
+ comment_form_email_label : "Email address"
45
+ comment_form_website_label : "Website (optional)"
46
+ comment_btn_submit : "Submit comment"
47
+ comment_btn_submitted : "Submitted"
48
+ comment_success_msg : "Thanks for your comment! It will show on the site once it has been approved."
49
+ comment_error_msg : "Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again."
50
+ loading_label : "Loading..."
51
+ search_label_text : "Enter your search term..."
52
+ search_placeholder_text : "Enter your search term..."
53
+ search_algolia_no_results : "No results"
54
+ results_found : "Result(s) found"
55
+ back_to_top : "Back to top"
56
+ # Statistics labels
57
+ stats_total_posts : "Total Posts"
58
+ stats_total_pages : "Total Pages"
59
+ stats_categories : "Categories"
60
+ stats_tags : "Tags"
61
+ stats_last_updated : "Last Updated"
62
+ stats_overview : "Overview"
63
+ stats_content_breakdown : "Content Breakdown"
64
+ # Theme-specific labels
65
+ theme_toggle : "Toggle theme"
66
+ dark_mode : "Dark mode"
67
+ light_mode : "Light mode"
68
+ view_source : "View source"
69
+ edit_page : "Edit this page"
70
+ report_issue : "Report an issue"
71
+
72
+ en-US:
73
+ <<: *DEFAULT_EN
74
+ en-CA:
75
+ <<: *DEFAULT_EN
76
+ en-GB:
77
+ <<: *DEFAULT_EN
78
+ en-AU:
79
+ <<: *DEFAULT_EN
80
+
81
+ # Spanish
82
+ # -------
83
+ es: &DEFAULT_ES
84
+ skip_links : "Saltar enlaces"
85
+ skip_primary_nav : "Saltar a navegación principal"
86
+ skip_content : "Saltar a contenido"
87
+ skip_footer : "Saltar a pie"
88
+ page : "Página"
89
+ pagination_previous : "Anterior"
90
+ pagination_next : "Siguiente"
91
+ breadcrumb_home_label : "Inicio"
92
+ breadcrumb_separator : "/"
93
+ menu_label : "Alternar menú"
94
+ search_label : "Alternar búsqueda"
95
+ toc_label : "En esta página"
96
+ ext_link_label : "Enlace directo"
97
+ less_than : "menos de"
98
+ minute_read : "minuto(s) de lectura"
99
+ share_on_label : "Compartir en"
100
+ meta_label :
101
+ tags_label : "Etiquetas:"
102
+ categories_label : "Categorías:"
103
+ date_label : "Actualizado:"
104
+ author_label : "Autor:"
105
+ comments_label : "Deja un comentario"
106
+ comments_title : "Comentarios"
107
+ more_label : "Ver más"
108
+ related_label : "Puede que también te interese"
109
+ follow_label : "Seguir:"
110
+ feed_label : "Feed"
111
+ powered_by : "Funciona con"
112
+ website_label : "Sitio web"
113
+ email_label : "Correo electrónico"
114
+ recent_posts : "Entradas recientes"
115
+ undefined_wpm : "El parámetro words_per_minute (palabras por minuto) no está definido en _config.yml"
116
+ comment_form_info : "Tu dirección de correo electrónico no se publicará. Los campos obligatorios están marcados"
117
+ comment_form_comment_label : "Comentario"
118
+ comment_form_md_info : "Puedes utilizar Markdown"
119
+ comment_form_name_label : "Nombre"
120
+ comment_form_email_label : "Dirección de correo electrónico"
121
+ comment_form_website_label : "Sitio web (opcional)"
122
+ comment_btn_submit : "Enviar comentario"
123
+ comment_btn_submitted : "Enviado"
124
+ comment_success_msg : "¡Gracias por tu comentario! Se mostrará en el sitio una vez que haya sido aprobado."
125
+ comment_error_msg : "Lo sentimos, ha habido un error con tu envío. Por favor, asegúrate de que todos los campos obligatorios se han completado e inténtalo de nuevo."
126
+ loading_label : "Cargando..."
127
+ search_label_text : "Introduce tu término de búsqueda..."
128
+ search_placeholder_text : "Introduce tu término de búsqueda..."
129
+ search_algolia_no_results : "Sin resultados"
130
+ results_found : "Resultado(s) encontrado(s)"
131
+ back_to_top : "Volver arriba"
132
+ # Statistics labels
133
+ stats_total_posts : "Total de Publicaciones"
134
+ stats_total_pages : "Total de Páginas"
135
+ stats_categories : "Categorías"
136
+ stats_tags : "Etiquetas"
137
+ stats_last_updated : "Última Actualización"
138
+ stats_overview : "Resumen"
139
+ stats_content_breakdown : "Desglose de Contenido"
140
+ # Theme-specific labels
141
+ theme_toggle : "Cambiar tema"
142
+ dark_mode : "Modo oscuro"
143
+ light_mode : "Modo claro"
144
+ view_source : "Ver código fuente"
145
+ edit_page : "Editar esta página"
146
+ report_issue : "Reportar un problema"
147
+
148
+ es-ES:
149
+ <<: *DEFAULT_ES
150
+ es-MX:
151
+ <<: *DEFAULT_ES
152
+
153
+ # French
154
+ # ------
155
+ fr: &DEFAULT_FR
156
+ skip_links : "Liens de saut"
157
+ skip_primary_nav : "Aller à la navigation principale"
158
+ skip_content : "Aller au contenu"
159
+ skip_footer : "Aller au pied de page"
160
+ page : "Page"
161
+ pagination_previous : "Précédent"
162
+ pagination_next : "Suivant"
163
+ breadcrumb_home_label : "Accueil"
164
+ breadcrumb_separator : "/"
165
+ menu_label : "Basculer le menu"
166
+ search_label : "Basculer la recherche"
167
+ toc_label : "Sur cette page"
168
+ ext_link_label : "Lien direct"
169
+ less_than : "moins de"
170
+ minute_read : "minute(s) de lecture"
171
+ share_on_label : "Partager sur"
172
+ meta_label :
173
+ tags_label : "Tags :"
174
+ categories_label : "Catégories :"
175
+ date_label : "Mis à jour :"
176
+ author_label : "Auteur :"
177
+ comments_label : "Laisser un commentaire"
178
+ comments_title : "Commentaires"
179
+ more_label : "En savoir plus"
180
+ related_label : "Vous pourriez aussi aimer"
181
+ follow_label : "Suivre :"
182
+ feed_label : "Flux"
183
+ powered_by : "Propulsé par"
184
+ website_label : "Site web"
185
+ email_label : "Email"
186
+ recent_posts : "Articles récents"
187
+ back_to_top : "Retour en haut"
188
+ # Statistics labels
189
+ stats_total_posts : "Total des Publications"
190
+ stats_total_pages : "Total des Pages"
191
+ stats_categories : "Catégories"
192
+ stats_tags : "Tags"
193
+ stats_last_updated : "Dernière Mise à Jour"
194
+ stats_overview : "Aperçu"
195
+ stats_content_breakdown : "Répartition du Contenu"
196
+ # Theme-specific labels
197
+ theme_toggle : "Changer de thème"
198
+ dark_mode : "Mode sombre"
199
+ light_mode : "Mode clair"
200
+ view_source : "Voir la source"
201
+ edit_page : "Modifier cette page"
202
+ report_issue : "Signaler un problème"
203
+
204
+ fr-FR:
205
+ <<: *DEFAULT_FR
206
+ fr-CA:
207
+ <<: *DEFAULT_FR
208
+
209
+ # German
210
+ # ------
211
+ de: &DEFAULT_DE
212
+ skip_links : "Links überspringen"
213
+ skip_primary_nav : "Zur Hauptnavigation springen"
214
+ skip_content : "Zum Inhalt springen"
215
+ skip_footer : "Zur Fußzeile springen"
216
+ page : "Seite"
217
+ pagination_previous : "Zurück"
218
+ pagination_next : "Weiter"
219
+ breadcrumb_home_label : "Startseite"
220
+ breadcrumb_separator : "/"
221
+ menu_label : "Menü umschalten"
222
+ search_label : "Suche umschalten"
223
+ toc_label : "Auf dieser Seite"
224
+ ext_link_label : "Direktlink"
225
+ less_than : "weniger als"
226
+ minute_read : "Minute(n) Lesezeit"
227
+ share_on_label : "Teilen auf"
228
+ meta_label :
229
+ tags_label : "Tags:"
230
+ categories_label : "Kategorien:"
231
+ date_label : "Aktualisiert:"
232
+ author_label : "Autor:"
233
+ comments_label : "Einen Kommentar hinterlassen"
234
+ comments_title : "Kommentare"
235
+ more_label : "Mehr erfahren"
236
+ related_label : "Das könnte Sie auch interessieren"
237
+ follow_label : "Folgen:"
238
+ feed_label : "Feed"
239
+ powered_by : "Betrieben mit"
240
+ website_label : "Webseite"
241
+ email_label : "E-Mail"
242
+ recent_posts : "Neueste Beiträge"
243
+ back_to_top : "Nach oben"
244
+ # Statistics labels
245
+ stats_total_posts : "Gesamte Beiträge"
246
+ stats_total_pages : "Gesamte Seiten"
247
+ stats_categories : "Kategorien"
248
+ stats_tags : "Tags"
249
+ stats_last_updated : "Zuletzt Aktualisiert"
250
+ stats_overview : "Übersicht"
251
+ stats_content_breakdown : "Inhaltsaufschlüsselung"
252
+ # Theme-specific labels
253
+ theme_toggle : "Thema wechseln"
254
+ dark_mode : "Dunkler Modus"
255
+ light_mode : "Heller Modus"
256
+ view_source : "Quelle ansehen"
257
+ edit_page : "Diese Seite bearbeiten"
258
+ report_issue : "Ein Problem melden"
259
+
260
+ de-DE:
261
+ <<: *DEFAULT_DE
262
+ de-AT:
263
+ <<: *DEFAULT_DE
264
+ de-CH:
265
+ <<: *DEFAULT_DE
266
+
267
+ # Arabic
268
+ # ------
269
+ ar: &DEFAULT_AR
270
+ skip_links : "تخطي الروابط"
271
+ skip_primary_nav : "انتقل إلى التنقل الرئيسي"
272
+ skip_content : "انتقل إلى المحتوى"
273
+ skip_footer : "انتقل إلى التذييل"
274
+ page : "صفحة"
275
+ pagination_previous : "السابق"
276
+ pagination_next : "التالي"
277
+ breadcrumb_home_label : "الرئيسية"
278
+ breadcrumb_separator : "/"
279
+ menu_label : "تبديل القائمة"
280
+ search_label : "تبديل البحث"
281
+ toc_label : "في هذه الصفحة"
282
+ ext_link_label : "رابط مباشر"
283
+ less_than : "أقل من"
284
+ minute_read : "دقيقة للقراءة"
285
+ share_on_label : "مشاركة على"
286
+ meta_label :
287
+ tags_label : "الوسوم:"
288
+ categories_label : "الأقسام:"
289
+ date_label : "تاريخ التحديث:"
290
+ author_label : "الكاتب:"
291
+ comments_label : "اترك تعليقاً"
292
+ comments_title : "التعليقات"
293
+ more_label : "اقرأ المزيد"
294
+ related_label : "قد يعجبك أيضاً"
295
+ follow_label : "تابعنا:"
296
+ feed_label : "الخلاصة"
297
+ powered_by : "يعمل بواسطة"
298
+ website_label : "الموقع"
299
+ email_label : "البريد الإلكتروني"
300
+ recent_posts : "المنشورات الأخيرة"
301
+ back_to_top : "العودة للأعلى"
302
+ # Statistics labels
303
+ stats_total_posts : "إجمالي المنشورات"
304
+ stats_total_pages : "إجمالي الصفحات"
305
+ stats_categories : "الأقسام"
306
+ stats_tags : "الوسوم"
307
+ stats_last_updated : "آخر تحديث"
308
+ stats_overview : "نظرة عامة"
309
+ stats_content_breakdown : "تفصيل المحتوى"
310
+ # Theme-specific labels
311
+ theme_toggle : "تبديل المظهر"
312
+ dark_mode : "الوضع الداكن"
313
+ light_mode : "الوضع الفاتح"
314
+ view_source : "عرض المصدر"
315
+ edit_page : "تحرير هذه الصفحة"
316
+ report_issue : "الإبلاغ عن مشكلة"
317
+
318
+ ar-SA:
319
+ <<: *DEFAULT_AR
320
+ ar-EG:
321
+ <<: *DEFAULT_AR
@@ -0,0 +1,126 @@
1
+ #!/bin/bash
2
+
3
+ # Automated Content Statistics Update Script
4
+ # Used by: CI/CD pipelines for automatic statistics updates
5
+ # Purpose: Update statistics and optionally commit changes
6
+
7
+ set -euo pipefail
8
+
9
+ # Configuration
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
12
+ STATS_FILE="$SCRIPT_DIR/content_statistics.yml"
13
+
14
+ # Colors for output (if terminal supports it)
15
+ if [[ -t 1 ]]; then
16
+ RED='\033[0;31m'
17
+ GREEN='\033[0;32m'
18
+ YELLOW='\033[1;33m'
19
+ BLUE='\033[0;34m'
20
+ NC='\033[0m'
21
+ else
22
+ RED=''
23
+ GREEN=''
24
+ YELLOW=''
25
+ BLUE=''
26
+ NC=''
27
+ fi
28
+
29
+ echo -e "${BLUE}🤖 Automated Content Statistics Update${NC}"
30
+ echo "========================================"
31
+
32
+ # Check if this is a CI environment
33
+ if [[ "${CI:-false}" == "true" ]]; then
34
+ echo -e "${YELLOW}🔧 Running in CI environment${NC}"
35
+ # Set git user for automated commits
36
+ git config --global user.name "Content Statistics Bot"
37
+ git config --global user.email "bot@zer0-mistakes.dev"
38
+ fi
39
+
40
+ # Store current statistics hash if file exists
41
+ CURRENT_HASH=""
42
+ if [[ -f "$STATS_FILE" ]]; then
43
+ if command -v sha256sum &> /dev/null; then
44
+ CURRENT_HASH=$(sha256sum "$STATS_FILE" | cut -d' ' -f1)
45
+ elif command -v shasum &> /dev/null; then
46
+ CURRENT_HASH=$(shasum -a 256 "$STATS_FILE" | cut -d' ' -f1)
47
+ fi
48
+ fi
49
+
50
+ # Generate new statistics
51
+ echo -e "${YELLOW}🔄 Generating updated statistics...${NC}"
52
+ if bash "$SCRIPT_DIR/generate_statistics.sh"; then
53
+ echo -e "${GREEN}✅ Statistics generated successfully${NC}"
54
+ else
55
+ echo -e "${RED}❌ Failed to generate statistics${NC}"
56
+ exit 1
57
+ fi
58
+
59
+ # Check if statistics have changed
60
+ NEW_HASH=""
61
+ if [[ -f "$STATS_FILE" ]]; then
62
+ if command -v sha256sum &> /dev/null; then
63
+ NEW_HASH=$(sha256sum "$STATS_FILE" | cut -d' ' -f1)
64
+ elif command -v shasum &> /dev/null; then
65
+ NEW_HASH=$(shasum -a 256 "$STATS_FILE" | cut -d' ' -f1)
66
+ fi
67
+ fi
68
+
69
+ if [[ "$CURRENT_HASH" == "$NEW_HASH" ]]; then
70
+ echo -e "${BLUE}ℹ️ No changes detected in content statistics${NC}"
71
+ echo "Statistics are up to date."
72
+ exit 0
73
+ fi
74
+
75
+ echo -e "${GREEN}📊 Content statistics have been updated${NC}"
76
+
77
+ # If running in CI, commit the changes
78
+ if [[ "${CI:-false}" == "true" ]]; then
79
+ echo -e "${YELLOW}📝 Committing updated statistics...${NC}"
80
+
81
+ cd "$PROJECT_ROOT"
82
+
83
+ # Add the updated statistics file
84
+ git add "$STATS_FILE"
85
+
86
+ # Check if there are changes to commit
87
+ if git diff --staged --quiet; then
88
+ echo -e "${BLUE}ℹ️ No changes to commit${NC}"
89
+ else
90
+ # Create commit message with timestamp
91
+ TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
92
+
93
+ # Try to get stats from yq, fallback to N/A
94
+ TOTAL_POSTS="N/A"
95
+ PUBLISHED="N/A"
96
+ CATEGORIES="N/A"
97
+
98
+ if command -v yq &> /dev/null; then
99
+ TOTAL_POSTS=$(yq '.overview.total_posts' "$STATS_FILE" 2>/dev/null || echo "N/A")
100
+ PUBLISHED=$(yq '.overview.published' "$STATS_FILE" 2>/dev/null || echo "N/A")
101
+ CATEGORIES=$(yq '.overview.total_categories' "$STATS_FILE" 2>/dev/null || echo "N/A")
102
+ fi
103
+
104
+ COMMIT_MSG="🤖 Auto-update content statistics - $TIMESTAMP
105
+
106
+ - Total posts analyzed: $TOTAL_POSTS
107
+ - Published posts: $PUBLISHED
108
+ - Categories tracked: $CATEGORIES
109
+
110
+ [skip ci]"
111
+
112
+ git commit -m "$COMMIT_MSG"
113
+ echo -e "${GREEN}✅ Statistics committed successfully${NC}"
114
+
115
+ # Push if configured
116
+ if [[ "${AUTO_PUSH:-false}" == "true" ]]; then
117
+ echo -e "${YELLOW}🚀 Pushing changes...${NC}"
118
+ git push origin HEAD
119
+ echo -e "${GREEN}✅ Changes pushed to repository${NC}"
120
+ fi
121
+ fi
122
+ fi
123
+
124
+ echo ""
125
+ echo "=========================="
126
+ echo -e "${GREEN}🎉 Statistics update complete!${NC}"
@@ -9,8 +9,12 @@
9
9
  style="height: 180px; object-fit: cover;"
10
10
  -%}
11
11
 
12
- Convention: All preview paths should be absolute (e.g., /assets/images/preview.png)
13
- External URLs (https://...) are also supported.
12
+ Convention: Preview paths can omit the /assets/ prefix.
13
+ The component will auto-prepend the assets_prefix from _config.yml.
14
+ Examples:
15
+ - /images/previews/my-image.png → /assets/images/previews/my-image.png
16
+ - /assets/images/previews/my-image.png → unchanged
17
+ - https://example.com/image.png → unchanged (external URL)
14
18
  {% endcomment %}
15
19
  {% assign img = include.src | default: site.teaser %}
16
20
  {% assign alt = include.alt | default: "Preview image" %}
@@ -18,9 +22,22 @@
18
22
  {% assign style = include.style %}
19
23
  {% assign loading = include.loading | default: "lazy" %}
20
24
 
21
- {% comment %} External URL or internal path {% endcomment %}
25
+ {% comment %} Get assets prefix configuration {% endcomment %}
26
+ {% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
27
+ {% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
28
+
29
+ {% comment %} Determine final image source {% endcomment %}
22
30
  {% if img contains '://' %}
31
+ {% comment %} External URL - use as-is {% endcomment %}
23
32
  {% assign final_src = img %}
33
+ {% elsif auto_prefix and img != blank %}
34
+ {% comment %} Check if path already has the assets prefix {% endcomment %}
35
+ {% if img contains assets_prefix %}
36
+ {% assign final_src = img | relative_url %}
37
+ {% else %}
38
+ {% comment %} Prepend assets prefix to the path {% endcomment %}
39
+ {% assign final_src = assets_prefix | append: img | relative_url %}
40
+ {% endif %}
24
41
  {% else %}
25
42
  {% assign final_src = img | relative_url %}
26
43
  {% endif %}
@@ -16,12 +16,21 @@
16
16
  <!-- Intro Section -->
17
17
 
18
18
  {% comment %}
19
- Determine the preview image path. All preview paths should be absolute
20
- starting with /assets/ or external URLs starting with ://
19
+ Determine the preview image path. Preview paths can omit the /assets/ prefix.
20
+ The component will auto-prepend the assets_prefix from _config.yml.
21
21
  {% endcomment %}
22
22
  {% assign preview_image = page.preview | default: site.info_banner %}
23
+ {% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
24
+ {% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
25
+
23
26
  {% if preview_image contains '://' %}
24
27
  {% assign preview_path = preview_image %}
28
+ {% elsif auto_prefix and preview_image != blank %}
29
+ {% if preview_image contains assets_prefix %}
30
+ {% assign preview_path = preview_image | relative_url %}
31
+ {% else %}
32
+ {% assign preview_path = assets_prefix | append: preview_image | relative_url %}
33
+ {% endif %}
25
34
  {% else %}
26
35
  {% assign preview_path = preview_image | relative_url %}
27
36
  {% endif %}
@@ -45,23 +54,23 @@
45
54
  </button>
46
55
  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="shareDropdownBottom">
47
56
  <li>
48
- <a class="dropdown-item" href="https://reddit.com/submit?url={{ page.url | absolute_url | url_encode }}&amp;title={{ page.title | url_encode }}" target="_blank">
57
+ <a class="dropdown-item" href="https://reddit.com/submit?url={{ site.url | append: page.url | url_encode }}&amp;title={{ page.title | url_encode }}" target="_blank">
49
58
  <i class="bi bi-reddit me-2"></i>Share on Reddit
50
59
  </a>
51
60
  </li>
52
61
  <li>
53
- <a class="dropdown-item" href="https://www.linkedin.com/sharing/share-offsite/?url={{ page.url | absolute_url | url_encode }}" target="_blank">
62
+ <a class="dropdown-item" href="https://www.linkedin.com/sharing/share-offsite/?url={{ site.url | append: page.url | url_encode }}" target="_blank">
54
63
  <i class="bi bi-linkedin me-2"></i>Share on LinkedIn
55
64
  </a>
56
65
  </li>
57
66
  <li>
58
- <a class="dropdown-item" href="https://twitter.com/intent/tweet?url={{ page.url | absolute_url | url_encode }}&text={{ page.title | url_encode }}" target="_blank">
67
+ <a class="dropdown-item" href="https://twitter.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank">
59
68
  <i class="bi bi-twitter me-2"></i>Share on Twitter
60
69
  </a>
61
70
  </li>
62
71
  <li><hr class="dropdown-divider"></li>
63
72
  <li>
64
- <button class="dropdown-item" onclick='navigator.clipboard.writeText("{{ page.url | absolute_url }}"); alert("Link copied to clipboard!");'>
73
+ <button class="dropdown-item" onclick='navigator.clipboard.writeText("{{ site.url | append: page.url }}"); alert("Link copied to clipboard!");'>
65
74
  <i class="bi bi-clipboard me-2"></i>Copy Link
66
75
  </button>
67
76
  </li>
@@ -23,8 +23,19 @@
23
23
  {%- assign page_large_image = page.header.og_image | default: page.header.overlay_image | default: page.header.image | absolute_url -%}
24
24
  {%- assign page_large_image = page_large_image | escape -%}
25
25
 
26
+ {%- comment -%} Handle preview image with configurable assets prefix {%- endcomment -%}
26
27
  {%- assign page_teaser_image = page.preview | default: site.og_image -%}
27
- {%- assign page_teaser_image = {{ site.public_folder }}/page_teaser_image | escape -%}
28
+ {%- assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' -%}
29
+ {%- assign auto_prefix = site.preview_images.auto_prefix | default: true -%}
30
+
31
+ {%- if page_teaser_image contains '://' -%}
32
+ {%- comment -%} External URL - use as-is {%- endcomment -%}
33
+ {%- elsif auto_prefix and page_teaser_image != blank -%}
34
+ {%- unless page_teaser_image contains assets_prefix -%}
35
+ {%- assign page_teaser_image = assets_prefix | append: page_teaser_image -%}
36
+ {%- endunless -%}
37
+ {%- endif -%}
38
+ {%- assign page_teaser_image = page_teaser_image | absolute_url | escape -%}
28
39
 
29
40
  {%- assign site_og_image = site.og_image | absolute_url -%}
30
41
  {%- assign site_og_image = site_og_image | escape -%}
@@ -140,15 +140,15 @@ layout: default
140
140
  <div class="share-buttons mb-4">
141
141
  <h5 class="mb-3">Share this notebook</h5>
142
142
  <div class="d-flex gap-2">
143
- <a href="https://twitter.com/intent/tweet?text={{ page.title | uri_escape }}&url={{ page.url | absolute_url | uri_escape }}"
143
+ <a href="https://twitter.com/intent/tweet?text={{ page.title | uri_escape }}&url={{ site.url | append: page.url | uri_escape }}"
144
144
  class="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer">
145
145
  <i class="bi bi-twitter"></i> Twitter
146
146
  </a>
147
- <a href="https://www.linkedin.com/sharing/share-offsite/?url={{ page.url | absolute_url | uri_escape }}"
147
+ <a href="https://www.linkedin.com/sharing/share-offsite/?url={{ site.url | append: page.url | uri_escape }}"
148
148
  class="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer">
149
149
  <i class="bi bi-linkedin"></i> LinkedIn
150
150
  </a>
151
- <a href="mailto:?subject={{ page.title | uri_escape }}&body={{ page.url | absolute_url | uri_escape }}"
151
+ <a href="mailto:?subject={{ page.title | uri_escape }}&body={{ site.url | append: page.url | uri_escape }}"
152
152
  class="btn btn-outline-primary btn-sm">
153
153
  <i class="bi bi-envelope"></i> Email
154
154
  </a>
@@ -33,6 +33,8 @@ module Jekyll
33
33
  'style' => 'retro pixel art, 8-bit video game aesthetic, vibrant colors, nostalgic, clean pixel graphics',
34
34
  'style_modifiers' => 'pixelated, retro gaming style, CRT screen glow effect, limited color palette',
35
35
  'output_dir' => 'assets/images/previews',
36
+ 'assets_prefix' => '/assets',
37
+ 'auto_prefix' => true,
36
38
  'auto_generate' => false,
37
39
  'collections' => ['posts', 'docs', 'quickstart']
38
40
  }.freeze
@@ -52,18 +54,37 @@ module Jekyll
52
54
  site = doc.site
53
55
  config = self.config(site)
54
56
 
57
+ # Normalize the preview path using assets_prefix
58
+ normalized_preview = normalize_preview_path(preview, config)
59
+
55
60
  # Build the full path
56
- preview_path = if preview.start_with?('/')
57
- File.join(site.source, preview)
58
- elsif preview.start_with?('http')
61
+ preview_path = if normalized_preview.start_with?('/')
62
+ File.join(site.source, normalized_preview.sub(/^\//, ''))
63
+ elsif normalized_preview.start_with?('http')
59
64
  return true # External URL, assume it exists
60
65
  else
61
- File.join(site.source, config['output_dir'], preview)
66
+ File.join(site.source, config['output_dir'], normalized_preview)
62
67
  end
63
68
 
64
69
  File.exist?(preview_path)
65
70
  end
66
71
 
72
+ # Normalize a preview path by adding assets_prefix if needed
73
+ def self.normalize_preview_path(preview, config)
74
+ return preview if preview.nil? || preview.to_s.strip.empty?
75
+ return preview if preview.start_with?('http')
76
+
77
+ assets_prefix = config['assets_prefix'] || '/assets'
78
+ auto_prefix = config['auto_prefix'] != false # Default to true
79
+
80
+ # If auto_prefix is enabled and path doesn't already contain assets_prefix
81
+ if auto_prefix && !preview.include?(assets_prefix)
82
+ "#{assets_prefix}#{preview}"
83
+ else
84
+ preview
85
+ end
86
+ end
87
+
67
88
  # Get the preview image path for a document
68
89
  def self.preview_path(doc)
69
90
  preview = doc.data['preview']
@@ -72,11 +93,11 @@ module Jekyll
72
93
  site = doc.site
73
94
  config = self.config(site)
74
95
 
75
- # If it's already a full path or URL, return as-is
76
- return preview if preview.start_with?('/') || preview.start_with?('http')
96
+ # If it's an external URL, return as-is
97
+ return preview if preview.start_with?('http')
77
98
 
78
- # Build relative path from output_dir
79
- "#{config['output_dir']}/#{preview}"
99
+ # Normalize path with assets_prefix
100
+ normalize_preview_path(preview, config)
80
101
  end
81
102
 
82
103
  # Get list of documents missing preview images