11 Commits

Author SHA1 Message Date
kbe
7adeb66c6c SEO recommendation in french 2025-08-19 12:18:51 +02:00
kbe
8ac4c6b675 feat: Better ez-toc 2025-08-19 12:14:29 +02:00
kbe
e662c7cda4 feat: Add post-content styles and improve breadcrumb and link styles 2025-08-19 12:05:46 +02:00
kbe
78c41cc071 All pages are displayed but only published must 2025-08-19 11:41:54 +02:00
kbe
140552a35f wip page links and display 2025-08-19 11:34:40 +02:00
kbe
dd3a0240f9 Prepare footer 2025-08-19 10:31:09 +02:00
kbe
1ea47eb2da Improve SEO content 2025-08-19 10:20:34 +02:00
kbe
686ece4479 feat: Add a breadcrumb 2025-08-19 09:54:49 +02:00
kbe
90340d03aa Improve css style 2025-08-19 09:21:27 +02:00
kbe
63869c3090 Compile sass for theme 2025-08-19 09:05:50 +02:00
kbe
17fb355560 Compile sass for theme 2025-08-19 09:01:27 +02:00
67 changed files with 3730 additions and 2304 deletions

View File

@@ -20,6 +20,8 @@ h1,h2,h3,h4,h5,h6 {
color: get-color("dark");
font-family: $font-family-poppins;
font-weight: $font-weight-semi-bold;
margin-top: 1rem;
margin-bottom: 1rem;
}
.theme-font-nunito {
h1,h2,h3,h4,h5,h6 {
@@ -30,6 +32,7 @@ h1,h2,h3,h4,h5,h6 {
p {
margin: 0;
line-height: 1.74; /* 26px */
color: #000;
@include breakpoint-less(md) {
line-height: 1.6; /* 24px */
}
@@ -105,4 +108,5 @@ i {
}
.icon-sm {
i { font-size: 0.9em; }
}
}

View File

@@ -0,0 +1,155 @@
// Breadcrumb Component
.breadcrumb-wrapper {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
padding: 1rem 0;
@media (max-width: 768px) {
padding: 0.75rem 0;
}
.breadcrumb {
display: flex;
flex-wrap: wrap;
align-items: center;
margin: 0;
padding: 0;
list-style: none;
font-size: 0.875rem;
line-height: 1.4;
@media (max-width: 576px) {
font-size: 0.8125rem;
}
.breadcrumb-item {
display: flex;
align-items: center;
&:not(:last-child) {
margin-right: 0.5rem;
// &::after {
// content: "/";
// display: inline-block;
// color: #6c757d;
// margin-left: 0.75rem;
// opacity: 0.7;
// font-weight: 500;
// @media (max-width: 576px) {
// margin-left: 0.5rem;
// }
// }
}
a {
color: #495057;
text-decoration: none;
font-weight: 500;
transition: all 0.2s ease-in-out;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
&:hover {
color: #007bff;
background-color: rgba(0, 123, 255, 0.1);
transform: translateY(-1px);
}
&:focus {
outline: 2px solid #007bff;
outline-offset: 1px;
}
i.fas {
margin-right: 0.375rem;
font-size: 0.75rem;
@media (max-width: 576px) {
margin-right: 0.25rem;
font-size: 0.6875rem;
}
}
}
&.active {
color: #6c757d;
font-weight: 600;
padding: 0.25rem 0.5rem;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@media (max-width: 768px) {
max-width: 150px;
}
@media (max-width: 576px) {
max-width: 120px;
}
}
}
// Responsive truncation for long breadcrumbs
@media (max-width: 576px) {
.breadcrumb-item {
&:not(:last-child):not(:nth-last-child(2)) {
display: none;
& + .breadcrumb-item::before {
content: "...";
margin: 0 0.5rem;
color: #6c757d;
font-weight: bold;
}
}
}
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
background: linear-gradient(135deg, #343a40 0%, #495057 100%);
border-bottom-color: #6c757d;
.breadcrumb {
.breadcrumb-item {
&:not(:last-child):not(:only-child)::after {
color: #adb5bd;
}
a {
color: #dee2e6;
&:hover {
color: #66b2ff;
background-color: rgba(102, 178, 255, 0.1);
}
&:focus {
outline-color: #66b2ff;
}
}
&.active {
color: #adb5bd;
}
}
}
}
}
// Accessibility improvements
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

View File

@@ -0,0 +1,144 @@
/* Styles for ez-toc-container */
#ez-toc-container {
background-color: transparent;
border: none;
border-radius: 0;
padding: 16px 0;
box-shadow: none;
max-width: 100%;
margin: 16px 0;
width: 100%;
.ez-toc-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 10px;
border-bottom: 2px solid #ddd;
padding-bottom: 5px;
}
.ez-toc-cssicon-toggle-label {
display: flex;
align-items: center;
cursor: pointer;
margin-bottom: 10px;
.ez-toc-icon-toggle-span {
margin-left: 8px;
}
}
.ez-toc-list {
list-style-type: none;
padding-left: 0;
/* Base styles for all list items */
.ez-toc-link {
display: block;
padding: 8px 0;
color: #333;
text-decoration: none;
border-bottom: 1px solid #f0f0f0;
&:hover {
background-color: #f9f9f9;
border-radius: 4px;
}
}
/* Hierarchy-based alignment */
.ez-toc-list-level-1 {
padding-left: 0;
.ez-toc-link {
font-size: 1em;
font-weight: 600;
}
}
.ez-toc-list-level-2 {
padding-left: 20px;
.ez-toc-link {
font-size: 0.95em;
font-weight: 500;
padding-left: 10px;
}
}
.ez-toc-list-level-3 {
padding-left: 40px;
.ez-toc-link {
font-size: 0.9em;
font-weight: 400;
padding-left: 10px;
}
}
.ez-toc-list-level-4 {
padding-left: 60px;
.ez-toc-link {
font-size: 0.85em;
font-weight: 400;
padding-left: 10px;
}
}
/* Add more levels if needed */
}
/* Responsive styles */
@media (max-width: 768px) {
padding: 12px 0;
.ez-toc-title {
font-size: 1.125rem;
}
.ez-toc-link {
padding: 6px 0;
font-size: 0.9em;
}
/* Adjust hierarchy padding for tablets */
.ez-toc-list-level-2 {
padding-left: 15px;
}
.ez-toc-list-level-3 {
padding-left: 30px;
}
.ez-toc-list-level-4 {
padding-left: 45px;
}
}
@media (max-width: 480px) {
padding: 10px 0;
.ez-toc-title {
font-size: 1rem;
}
.ez-toc-link {
padding: 4px 0;
font-size: 0.85em;
}
/* Adjust hierarchy padding for mobile */
.ez-toc-list-level-2 {
padding-left: 10px;
}
.ez-toc-list-level-3 {
padding-left: 20px;
}
.ez-toc-list-level-4 {
padding-left: 30px;
}
}
}

View File

@@ -0,0 +1,27 @@
/* Styles for tables */
figure.wp-block-table table {
border-collapse: collapse;
width: 100%;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
thead {
background-color: #f8f9fa;
color: #000;
}
th, td {
border: 1px solid #dee2e6;
padding: 0.5rem;
text-align: left;
}
th {
background-color: #f1f3f5;
font-weight: bold;
}
tbody tr:nth-child(odd) {
background-color: #f9f9f9;
}
}

View File

@@ -1,15 +1,15 @@
//
// Button styles //
//
a {
color: get-color("dark", 0.9);
text-decoration: none;
@include transition(linear 0.1s);
&:hover {
color: get-color("dark");
text-decoration: none;
}
}
// a {
// color: get-color("dark", 0.9);
// text-decoration: none;
// @include transition(linear 0.1s);
// &:hover {
// color: get-color("dark");
// text-decoration: none;
// }
// }
button {
background: transparent;
box-shadow: none;

View File

@@ -0,0 +1,5 @@
.post-content {
p {
line-height: 2rem;
}
}

View File

@@ -1,6 +1,31 @@
//
// Text Link Styles //
//
a {
// color: #007bff;
// color: get-color("dark", 0.8);
color: #000;
text-decoration: none;
&:hover {
color: #0056b3;
text-decoration: none;
}
}
.post-content {
a {
// color: #007bff;
color: get-color("dark", 0.8);
text-decoration: underline;
&:hover {
color: #0056b3;
text-decoration: none;
}
}
}
*[class*='text-link-'] {
color: get-color("dark", 0.9);
&:hover, &:focus {

View File

@@ -40,6 +40,7 @@
@import "elements/testimonial";
@import "elements/text-link";
@import "elements/timeline";
@import "elements/post-content";
//
// Import Components //
@@ -49,10 +50,14 @@
@import "components/fullscreen-menu";
@import "components/gallery";
@import "components/header";
@import "components/breadcrumb";
@import "components/masonry";
@import "components/portfolio";
@import "components/table";
@import "components/preloader";
@import "components/section";
@import "components/_ez-toc";
//
// Import Utilities //
@@ -62,4 +67,35 @@
//
// Import Custom Styles //
//
@import "helpers/custom";
@import "helpers/custom";
ul.wp-block-list {
margin-top: 1rem;
margin-bottom: 1rem;
}
figure.wp-block-image {
margin-top: 2rem;
margin-bottom: 2rem;
}
//
// Figcaption Styles //
//
figcaption, .wp-element-caption {
font-size: 0.9rem;
color: #aeaeae;
margin-top: 0.5em;
text-align: center;
// display: flex;
// align-items: center;
// justify-content: center;
}
figure.wp-block-image {
img {
margin: auto;
display: block;
}
}

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
assets/images/og-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/images/og-logo.xcf Normal file

Binary file not shown.

114
docs/SEO-IMPROVEMENTS.md Normal file
View File

@@ -0,0 +1,114 @@
# SEO Improvements Documentation
## Overview
This project has been enhanced with comprehensive SEO capabilities using a modular partial system in Hugo.
## File Structure
```
layouts/partials/seo/
├── seo-config.html # Main SEO configuration loader
├── seo-meta.html # Core SEO meta tags
├── opengraph.html # Open Graph tags for social media
├── twitter-cards.html # Twitter Card tags
├── structured-data.html # JSON-LD schema markup
└── favicons.html # Favicon variations and PWA support
```
## Features Added
### 1. Core SEO Meta Tags
- Dynamic meta description
- Keywords (with fallback)
- Author information
- Canonical URLs
- Robots meta tags
- Dublin Core metadata
- Geo tags (if configured)
### 2. Open Graph Tags
- og:title, og:description, og:image
- og:type (article/website)
- og:site_name
- og:url
- Article-specific tags for blog posts
### 3. Twitter Cards
- twitter:card (summary_large_image)
- twitter:title, twitter:description
- twitter:image
- Site and creator handles
### 4. Structured Data (JSON-LD)
- WebSite schema
- Article schema for blog posts
- BreadcrumbList schema
- Organization schema
### 5. Favicon & PWA Support
- Multiple favicon sizes
- Apple Touch Icons
- Android icons
- PWA manifest.json
- Theme colors
## Configuration
### Hugo Configuration (hugo.toml)
```toml
[params.seo]
description = "Your site description"
keywords = ["keyword1", "keyword2"]
author = "Your Name"
theme_color = "#007bff"
default_image = "/images/og-default.jpg"
logo = "/images/logo.png"
[params.seo.twitter]
site = "@yourhandle"
creator = "@yourhandle"
```
### Content Frontmatter
Add to your content's frontmatter:
```yaml
---
title: "Your Post Title"
description: "Detailed description for SEO"
keywords: ["seo", "hugo", "optimization"]
author: "Author Name"
image: "/images/post-image.jpg"
robots: "index, follow"
---
```
## Testing & Validation
### Recommended Tools
- Google Rich Results Test: https://search.google.com/test/rich-results
- Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/
- Twitter Card Validator: https://cards-dev.twitter.com/validator
- Schema.org Validator: https://validator.schema.org/
### Validation Checklist
- [ ] Meta tags present in page source
- [ ] Open Graph tags validate
- [ ] Twitter Cards validate
- [ ] JSON-LD schema validates
- [ ] Favicons load correctly
- [ ] Canonical URLs are correct
## Next Steps
1. Generate favicon files for all sizes (use a favicon generator)
2. Create og-default.jpg and twitter-default.jpg images
3. Set up Google Search Console and add verification code
4. Set up Bing Webmaster Tools
5. Test with social media sharing
## Fallback Values
The system includes intelligent fallback values:
- Description: Page → Site → Title
- Keywords: Page → Site → Empty string
- Image: Page → Site → Default
- Author: Page → Site → Site Title
All SEO improvements have been successfully implemented!

View File

@@ -0,0 +1,363 @@
# Recommandations SEO Avancées - Hugo Mistergeek
## Vue d'ensemble
Ce document fournit des recommandations SEO avancées spécifiquement adaptées au blog Hugo Mistergeek, un site francophone traitant de technologie et d'informatique. Les recommandations prennent en compte l'implémentation SEO existante et proposent des améliorations ciblées pour maximiser la visibilité sur le marché francophone.
## État Actuel de l'implémentation SEO ✅
### ✅ Implémenté avec succès
- **Meta tags essentiels** (description, keywords, author)
- **Open Graph** pour les réseaux sociaux
- **Twitter Cards** avec images optimisées
- **Schema.org** (JSON-LD) pour les données structurées
- **Favicons** multi-formats et support PWA
- **Canonical URLs** et hreflang
- **Génération de sitemap XML** via Hugo
## Recommandations d'Amélioration SEO
### 1. Optimisation du Contenu Francophone
#### Structure des URLs
```yaml
# Recommandation: Optimiser pour le français
Ancienne structure: /post/2023-11-wordpress-creation-site/
Nouvelle structure: /tutoriels/wordpress/creer-site-wordpress-guide-complet/
```
#### Stratégie de Mots-clés pour le Marché Francophone
```yaml
# Mots-clés principaux (haute concurrence)
- "tutoriel informatique"
- "guide technologie"
- "solutions informatiques"
# Mots-clés longue traîne (français)
- "comment créer un site WordPress en français"
- "meilleur antivirus gratuit pour Windows 10 en 2024"
- "tutoriel sécurité informatique débutant"
# Variations linguistiques
- Anglais: "computer tutorial" → Français: "tutoriel informatique"
- Anglais: "how to" → Français: "comment", "guide", "tutoriel"
```
### 2. Optimisation Technique Avancée
#### Performance et Core Web Vitals
```html
<!-- Ajouter dans layouts/partials/head-performance.html -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="//www.google-analytics.com">
<link rel="dns-prefetch" href="//googletagmanager.com">
```
#### Lazy Loading Stratégique
```html
<!-- Pour les images dans les articles -->
<img
src="/images/placeholder.svg"
data-src="/images/article-image.jpg"
alt="Tutoriel WordPress - Créer un site professionnel"
loading="lazy"
width="800"
height="400"
/>
```
### 3. Schema.org Amélioré
#### Article Schema avec Auteur Détaillé
```json
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Guide Complet WordPress 2024",
"description": "Créez votre site WordPress en français avec ce guide étape par étape",
"author": {
"@type": "Person",
"name": "Mistergeek",
"url": "https://www.mistergeek.net/",
"sameAs": [
"https://twitter.com/mistergeekfrance",
"https://www.linkedin.com/in/mistergeek"
]
},
"publisher": {
"@type": "Organization",
"name": "Mistergeek",
"logo": {
"@type": "ImageObject",
"url": "https://www.mistergeek.net/assets/images/logo.png"
}
},
"inLanguage": "fr-FR",
"datePublished": "2024-01-15",
"dateModified": "2024-01-15",
"keywords": ["wordpress", "tutoriel français", "créer site web"]
}
```
#### BreadcrumbList pour la Navigation
```json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Accueil",
"item": "https://www.mistergeek.net/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Tutoriels",
"item": "https://www.mistergeek.net/tutoriels/"
},
{
"@type": "ListItem",
"position": 3,
"name": "WordPress",
"item": "https://www.mistergeek.net/tutoriels/wordpress/"
}
]
}
```
### 4. SEO Local pour le Marché Français
#### Google My Business
```json
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Mistergeek",
"url": "https://www.mistergeek.net",
"logo": "https://www.mistergeek.net/assets/images/logo.png",
"contactPoint": {
"@type": "ContactPoint",
"contactType": "support",
"email": "contact@mistergeek.net",
"availableLanguage": ["French"]
},
"sameAs": [
"https://twitter.com/mistergeekfrance",
"https://www.youtube.com/@mistergeek"
]
}
```
### 5. Amélioration des Meta Tags
#### Meta Tags Dynamiques par Type de Contenu
```go
<!-- layouts/partials/seo/meta-dynamic.html -->
{{- if eq .Section "tutoriels" }}
<meta name="description" content="Tutoriel complet {{ .Title }}. Apprenez {{ .Params.skill }} en français avec notre guide étape par étape.">
<meta name="keywords" content="tutoriel {{ .Params.category }}, guide français {{ .Params.skill }}, {{ .Title | lower }}">
{{- else if eq .Section "comparatifs" }}
<meta name="description" content="Comparatif détaillé {{ .Title }}. Lequel choisir en 2024 ? Avis et tests complets.">
{{- end }}
```
### 6. Optimisation Multilingue
#### hreflang pour le Français
```html
<!-- Dans layouts/partials/seo/hreflang.html -->
<link rel="alternate" hreflang="fr-fr" href="https://www.mistergeek.net{{ .RelPermalink }}" />
<link rel="alternate" hreflang="fr-be" href="https://www.mistergeek.net/be{{ .RelPermalink }}" />
<link rel="alternate" hreflang="fr-ca" href="https://www.mistergeek.net/ca{{ .RelPermalink }}" />
<link rel="alternate" hreflang="x-default" href="https://www.mistergeek.net{{ .RelPermalink }}" />
```
### 7. Structure des Données pour les Tutoriels
#### Tutoriel Schema
```json
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Créer un site WordPress",
"description": "Guide complet pour créer votre site WordPress en français",
"image": "https://www.mistergeek.net/assets/images/wordpress-guide.jpg",
"totalTime": "PT30M",
"estimatedCost": {
"@type": "MonetaryAmount",
"currency": "EUR",
"value": "0"
},
"supply": [
{
"@type": "HowToSupply",
"name": "WordPress"
},
{
"@type": "HowToSupply",
"name": "Hébergement web"
}
],
"tool": [
{
"@type": "HowToTool",
"name": "WordPress"
}
],
"step": [
{
"@type": "HowToStep",
"name": "Installation WordPress",
"text": "Téléchargez et installez WordPress"
}
]
}
```
### 8. Optimisation des Images
#### Structure des Images SEO
```yaml
# config.toml - Configuration des images
[imaging]
quality = 75
resampleFilter = "lanczos"
[params.images]
# Dimensions recommandées pour le SEO
og_image = "1200x630"
twitter_image = "1200x675"
thumbnail = "400x300"
hero = "1920x1080"
```
### 9. Internal Linking Stratégique
#### Structure de Liens
```markdown
<!-- Dans le contenu -->
Pour aller plus loin, découvrez :
- [Guide complet WordPress](/tutoriels/wordpress/guide-complet/)
- [Meilleurs plugins WordPress](/comparatifs/plugins-wordpress-2024/)
- [Sécurité WordPress](/securite/wordpress-securite-maximale/)
```
### 10. Configuration Avancée Hugo.toml
```toml
# SEO Configuration avancée
[params.seo]
# Configuration existante...
# Analytics
google_analytics = "G-XXXXXXXXXX"
google_tag_manager = "GTM-XXXXXXX"
# Rich Snippets
enable_search_box = true
enable_sitelinks_searchbox = true
# Social
facebook_page = "mistergeek.fr"
twitter_handle = "@mistergeekfrance"
youtube_channel = "UCXXXXXXXXXXXXXXXXXXX"
# Local SEO
[params.seo.local]
enabled = true
country = "FR"
language = "fr"
region = "Île-de-France"
[markup.goldmark.renderer]
unsafe = true
[markup.highlight]
style = "github"
lineNos = true
codeFences = true
[sitemap]
changefreq = "weekly"
filename = "sitemap.xml"
priority = 0.5
[privacy]
[privacy.googleAnalytics]
disable = false
respectDoNotTrack = true
```
### 11. Monitoring et Outils
#### Outils de Monitoring SEO
```yaml
# À configurer
- Google Search Console: vérifier le site
- Google Analytics 4: tracking avancé
- Bing Webmaster Tools: indexation Bing
- Yandex Webmaster: marché russe
- Ahrefs/SEMrush: analyse concurrentielle
```
### 12. Checklist de Lancement
#### Pré-lancement SEO
- [ ] Vérifier tous les meta tags
- [ ] Tester les rich snippets
- [ ] Valider le sitemap.xml
- [ ] Configurer Google Search Console
- [ ] Configurer Google Analytics 4
- [ ] Tester la vitesse de chargement
- [ ] Vérifier les images manquantes
- [ ] Tester les liens cassés
- [ ] Valider le markup Schema.org
#### Post-lancement
- [ ] Soumettre le sitemap à Google
- [ ] Monitorer les erreurs 404
- [ ] Analyser les mots-clés de positionnement
- [ ] Optimiser les pages avec faible CTR
- [ ] Améliorer les pages avec fort taux de rebond
### 13. Mesure de Performance
#### KPIs SEO à suivre
```yaml
Mensuel:
- Position moyenne des mots-clés
- Taux de clic (CTR)
- Pages indexées
- Erreurs de crawl
- Vitesse de chargement
Trimestriel:
- Part de marché des mots-clés
- Backlinks de qualité
- Authority Score (Domain Rating)
- Trafic organique vs objectifs
```
## Ressources Additionnelles
### Outils Français SEO
- **Google Search Console** - Monitoring principal
- **Screaming Frog** - Audit technique
- **Ahrefs/SEMrush** - Analyse concurrentielle
- **GTmetrix** - Performance
- **Schema Markup Validator** - Rich snippets
### Documentation
- [Google SEO Starter Guide - Français](https://support.google.com/webmasters/answer/7451184?hl=fr)
- [Bing Webmaster Guidelines](https://www.bing.com/webmaster/help/webmaster-guidelines-30fba23a)
- [Schema.org Documentation](https://schema.org/docs/documents.html)
### Support Communautaire
- [Webmaster Help Community - Français](https://support.google.com/webmasters/community?hl=fr)
- [Reddit r/SEO](https://www.reddit.com/r/SEO/)
- [Search Engine Journal - French](https://www.searchenginejournal.com/tag/french/)
Ce guide SEO est spécifiquement adapté au marché francophone et prend en compte les particularités linguistiques et culturelles du public cible de Mistergeek.

View File

@@ -13,6 +13,32 @@ ignoreLogs = ["warning-goldmark-raw-html"]
[markup.goldmark.renderer]
unsafe = true
# SEO Configuration
[params.seo]
description = "Mistergeek - Tutoriels et guides en informatique"
keywords = ["développement web", "technologies", "innovation", "solutions digitales", "mistergeek"]
author = "Mistergeek"
theme_color = "#007bff"
default_image = "/assets/images/og-logo.png"
logo = "/assets/images/logo.png"
# Social Media
[params.seo.twitter]
site = "@mistergeekfrance"
creator = "@mistergeekfrance"
# Search Engine Verification
# google_verification = "your-google-verification-code"
# bing_verification = "your-bing-verification-code"
# yandex_verification = "your-yandex-verification-code"
# Geo Location (if applicable)
# [params.seo.geo]
# region = "FR-IDF"
# placename = "Paris"
# latitude = "48.8566"
# longitude = "2.3522"
# WordPress API Configuration
[params.wordpress]
apiUrl = "https://www.mistergeek.net/wp-json/wp/v2"
@@ -31,3 +57,20 @@ ignoreLogs = ["warning-goldmark-raw-html"]
[[build.cachebusters]]
source = "assets/.*\\.(css|sass|scss)$"
target = "css"
# Output formats for search index
[outputs]
home = ["HTML", "RSS", "JSON"]
[outputFormats]
[outputFormats.JSON]
mediaType = "application/json"
baseName = "search-index"
isPlainText = true
notAlternative = true
# Search configuration
[params.search]
enabled = true
minQueryLength = 2
maxResults = 10

View File

@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ .Site.Language.Lang | default "en" }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="{{ if .Description }}{{ .Description }}{{ else }}{{ .Site.Title }}{{ end }}">
<meta name="keywords" content="">
<!-- SEO & Social Meta Tags -->
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}</title>
<!-- Favicon -->
<link href="/assets/images/favicon.png" rel="shortcut icon">
{{ partial "seo/seo-config.html" . }}
<!-- CSS -->
<link href="/assets/plugins/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/assets/plugins/owl-carousel/owl.carousel.min.css" rel="stylesheet">
@@ -16,6 +16,7 @@
<link href="/assets/plugins/scrollcue/scrollcue.css" rel="stylesheet">
<link href="/assets/plugins/swiper/swiper-bundle.min.css" rel="stylesheet">
<link href="/assets/css/theme.css" rel="stylesheet">
<link href="/assets/css/theme-colors/theme-color-blue.css" rel="stylesheet">
<!-- Fonts/Icons -->
<link href="/assets/plugins/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<link href="/assets/plugins/font-awesome/css/all.css" rel="stylesheet">
@@ -25,34 +26,15 @@
<!-- Header -->
{{ partial "header.html" . }}
<!-- Breadcrumb -->
{{ partial "breadcrumb.html" . }}
<main>
{{ block "main" . }}{{ end }}
</main>
<footer>
<div class="section bg-dark">
<div class="container">
<div class="row g-4 align-items-center">
<div class="col-12 col-md-6 text-center text-md-start">
<ul class="list-inline-dash">
<li><a href="/faq/">FAQ</a></li>
<li><a href="/careers/">Careers</a></li>
<li><a href="/clients/">Clients</a></li>
</ul>
<p class="mt-2">&copy; {{ now.Format "2006" }} {{ .Site.Title }}, All Rights Reserved.</p>
</div>
<div class="col-12 col-md-6 text-center text-md-end">
<ul class="list-inline">
<li><a href="#"><i class="bi bi-facebook"></i></a></li>
<li><a href="#"><i class="bi bi-twitter-x"></i></a></li>
<li><a href="#"><i class="bi bi-pinterest"></i></a></li>
<li><a href="#"><i class="bi bi-instagram"></i></a></li>
</ul>
</div>
</div><!-- end row -->
</div><!-- end container -->
</div>
</footer>
<!-- Footer -->
{{ partial "footer.html" . }}
<!-- Scroll to top button -->
<div class="scrolltotop icon-lg">
@@ -64,5 +46,6 @@
<script src="/assets/plugins/jquery.min.js"></script>
<script src="/assets/plugins/plugins.js"></script>
<script src="/assets/js/functions.js"></script>
{{ partial "scripts.html" . }}
</body>
</html>

View File

@@ -7,15 +7,11 @@
<ul class="list-inline-dash">
{{ if .Params.author }}
<li><a href="#">{{ .Params.author }}</a></li>
{{ else }}
<li><a href="#">by Admin</a></li>
{{ end }}
{{ with .Params.categories }}
{{ range . }}
<li><a href="/categories/{{ . | urlize }}">{{ . }}</a></li>
{{ end }}
{{ else }}
<li><a href="/categories/non-classe">Non classé</a></li>
{{ end }}
<li><a href="#">{{ .Date.Format "Jan 2, 2006" }}</a></li>
</ul>

View File

@@ -0,0 +1,70 @@
<!-- Breadcrumb Navigation with SEO optimization -->
<div class="breadcrumb-wrapper">
<div class="container">
<div class="row">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
<!-- Home/Accueil with SEO -->
<li class="breadcrumb-item" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a href="{{ "/" | relLangURL }}" itemprop="item" title="{{ i18n "home" | default "Accueil" }}">
<span itemprop="name">{{ i18n "home" | default "Accueil" }}</span>
</a>
<meta itemprop="position" content="1" />
</li>
<!-- Category with SEO and linking -->
{{ if or .Params.categories .Section }}
{{ $category := "" }}
{{ $categorySlug := "" }}
{{ $categoryUrl := "" }}
{{ if .Params.categories }}
{{ $category = index .Params.categories 0 }}
{{ $categorySlug = urlize $category }}
{{ $categoryUrl = printf "/categories/%s" $categorySlug }}
{{ else if .Section }}
{{ $category = humanize .Section }}
{{ $categorySlug = .Section }}
{{ $categoryUrl = printf "/%s" .Section }}
{{ end }}
{{ if $category }}
<li class="breadcrumb-item active" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem" aria-current="page">
<a href="{{ $categoryUrl | relLangURL }}" itemprop="item" title="{{ $category }}">
<span itemprop="name">{{ $category }}</span>
</a>
<meta itemprop="position" content="2" />
</li>
{{ end }}
{{ end }}
</ol>
</nav>
</div>
</div>
</div>
</div>
<!-- JSON-LD Structured Data for SEO -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "{{ i18n "home" | default "Accueil" }}",
"item": "{{ "/" | absLangURL }}"
}{{ if or .Params.categories .Section }},
{
"@type": "ListItem",
"position": 2,
"name": "{{ if .Params.categories }}{{ index .Params.categories 0 }}{{ else }}{{ humanize .Section }}{{ end }}",
"item": "{{ if .Params.categories }}{{ printf "%s/categories/%s" (absLangURL "") (urlize (index .Params.categories 0)) }}{{ else }}{{ printf "%s/%s" (absLangURL "") .Section }}{{ end }}"
}{{ end }}
]
}
</script>

View File

@@ -0,0 +1,66 @@
<footer>
<div class="section-sm bg-dark">
<div class="container">
<div class="row g-4">
<div class="col-6 col-sm-6 col-lg-3">
<h3 class="uppercase letter-spacing-1">{{ .Site.Title }}</h3>
</div>
<div class="col-6 col-sm-6 col-lg-3">
<h6 class="font-small fw-medium uppercase">Pages</h6>
<ul class="list-unstyled">
<li><a href="/">Accueil</a></li>
{{ if .Site.Data.wordpress }}
{{ range $index, $element := .Site.Data.wordpress.navigation }}
<li>
<a href="/{{ $element.slug }}">{{ $element.title }}</a>
</li>
{{ end }}
{{ end }}
</ul>
</div>
<div class="col-6 col-sm-6 col-lg-3">
<h6 class="font-small fw-medium uppercase">Toutes les catégories</h6>
<ul class="list-unstyled">
{{ if .Site.Data.wordpress }}
{{ $count := 0 }}
{{ range $index, $element := .Site.Data.wordpress.categories }}
<li class="nav-item">
<a class="nav-link" href="/categories/{{ $element.slug }}">{{ $element.name }}</a>
</li>
{{ end }}
{{ end }}
</ul>
</div>
<div class="col-6 col-sm-6 col-lg-3">
<h6 class="font-small fw-medium uppercase">Contact Info</h6>
<ul class="list-unstyled">
<li>121 King St, Melbourne VIC 3000</li>
<li>contact@example.com</li>
<li>+(123) 456 789 01</li>
</ul>
</div>
</div><!-- end row -->
</div><!-- end container -->
</div>
<div class="bg-black py-4">
<div class="container">
<div class="row align-items-center g-2 g-lg-3">
<div class="col-12 col-md-6 text-center text-md-start">
<p>&copy; {{ now.Format "2006" }} {{ .Site.Title }}, All Rights Reserved.</p>
</div>
<div class="col-12 col-md-6 text-center text-md-end">
<ul class="list-inline-sm">
<li><a class="button-circle button-circle-sm button-circle-social-facebook" href="#"><i class="bi bi-facebook"></i></a></li>
<li><a class="button-circle button-circle-sm button-circle-social-twitter" href="#"><i class="bi bi-twitter-x"></i></a></li>
<li><a class="button-circle button-circle-sm button-circle-social-pinterest" href="#"><i class="bi bi-pinterest"></i></a></li>
<li><a class="button-circle button-circle-sm button-circle-social-instagram" href="#"><i class="bi bi-instagram"></i></a></li>
</ul>
</div>
</div><!-- end row -->
</div><!-- end container -->
</div>
</footer>

View File

@@ -0,0 +1,46 @@
<!-- Header -->
<div class="header center header-color-dark">
<div class="container">
<!-- Logo -->
<div class="header-logo">
<h3 class="uppercase letter-spacing-1"><a href="/">{{ .Site.Title }}</a></h3>
</div>
<!-- Menu -->
<div class="header-menu">
<ul class="nav">
<li class="nav-item">
<a class="nav-link" href="/">Accueil</a>
</li>
{{ if .Site.Data.wordpress }}
{{ $count := 0 }}
{{ range $index, $element := .Site.Data.wordpress.categories }}
{{ if or (eq $element.name "Featured") (eq $element.name "Non classé") }}
{{ continue }}
{{ else if lt $count 5 }}
<li class="nav-item">
<a class="nav-link" href="/categories/{{ $element.slug }}">{{ $element.name }}</a>
</li>
{{ $count = add $count 1 }}
{{ else }}
{{ break }}
{{ end }}
{{ end }}
{{ end }}
</ul>
</div>
<!-- Menu Extra -->
{{/* <div class="header-menu-extra">
<ul class="list-inline-sm">
<li><a href="#"><i class="bi bi-facebook"></i></a></li>
<li><a href="#"><i class="bi bi-twitter-x"></i></a></li>
<li><a href="#"><i class="bi bi-linkedin"></i></a></li>
</ul>
</div> */}}
<!-- Menu Toggle -->
<button class="header-toggle">
<span></span>
</button>
</div><!-- end container -->
</div>
<!-- end Header -->

View File

@@ -0,0 +1,3 @@
<!-- Search Scripts -->
<script src="https://cdn.jsdelivr.net/npm/lunr@2.3.9/lunr.min.js"></script>
<script src="/assets/js/search.js"></script>

View File

@@ -0,0 +1,32 @@
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="/assets/images/favicon.ico">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" sizes="57x57" href="/assets/images/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/assets/images/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/assets/images/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/assets/images/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/assets/images/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/assets/images/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/assets/images/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/assets/images/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon-180x180.png">
<!-- Android Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/assets/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="{{ .Site.Params.seo.theme_color | default "#007bff" }}">
<meta name="msapplication-TileImage" content="/assets/images/ms-icon-144x144.png">
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Theme Color -->
<meta name="theme-color" content="{{ .Site.Params.seo.theme_color | default "#007bff" }}">
<meta name="msapplication-TileColor" content="{{ .Site.Params.seo.theme_color | default "#007bff" }}">

View File

@@ -0,0 +1,33 @@
{{- $title := .Title | default .Site.Title -}}
{{- $description := .Description | default .Summary | default .Site.Params.description | default .Site.Title -}}
{{- $image := .Params.image | default .Site.Params.seo.default_image | default "/assets/images/og-default.jpg" -}}
{{- $image = $image | absURL -}}
{{- $url := .Permalink | default .RelPermalink | absURL -}}
{{- $siteName := .Site.Title -}}
{{- $locale := .Site.Language.Lang | default "en_US" -}}
<!-- Open Graph / Facebook -->
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:site_name" content="{{ $siteName }}">
<meta property="og:title" content="{{ $title }}">
<meta property="og:description" content="{{ $description }}">
<meta property="og:url" content="{{ $url }}">
<meta property="og:locale" content="{{ $locale }}">
<meta property="og:image" content="{{ $image }}">
<meta property="og:image:alt" content="{{ $title }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<!-- Article specific -->
{{ if .IsPage }}
<meta property="article:section" content="{{ .Section | default "general" }}">
<meta property="article:author" content="{{ .Params.author | default .Site.Params.author | default .Site.Title }}">
{{ range .Params.tags | default .Site.Params.tags }}
<meta property="article:tag" content="{{ . }}">
{{ end }}
{{ end }}
<!-- Additional locales for multilingual sites -->
{{ range .Translations }}
<meta property="og:locale:alternate" content="{{ .Language.Lang | default "en_US" }}">
{{ end }}

View File

@@ -0,0 +1,42 @@
{{- /* SEO Configuration Partial */ -}}
{{- /* This partial includes all SEO-related partials */ -}}
<!-- Core SEO Meta Tags -->
{{ partial "seo/seo-meta.html" . }}
<!-- Open Graph Tags -->
{{ partial "seo/opengraph.html" . }}
<!-- Twitter Cards -->
{{ partial "seo/twitter-cards.html" . }}
<!-- Structured Data (JSON-LD) -->
{{ partial "seo/structured-data.html" . }}
<!-- Favicons and PWA Support -->
{{ partial "seo/favicons.html" . }}
<!-- Additional SEO Tags -->
{{- if .Site.Params.seo.google_verification }}
<meta name="google-site-verification" content="{{ .Site.Params.seo.google_verification }}">
{{ end }}
{{- if .Site.Params.seo.bing_verification }}
<meta name="msvalidate.01" content="{{ .Site.Params.seo.bing_verification }}">
{{ end }}
{{- if .Site.Params.seo.yandex_verification }}
<meta name="yandex-verification" content="{{ .Site.Params.seo.yandex_verification }}">
{{ end }}
{{- if .Site.Params.seo.alexa_verification }}
<meta name="alexaVerifyID" content="{{ .Site.Params.seo.alexa_verification }}">
{{ end }}
<!-- hreflang for multilingual sites -->
{{ range .Translations }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{ end }}
{{ if .IsTranslated }}
<link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
{{ end }}

View File

@@ -0,0 +1,40 @@
{{- $description := .Description | default .Summary | default .Site.Params.description | default .Site.Title -}}
{{- $keywords := delimit (.Keywords | default .Site.Params.keywords | default (slice)) ", " -}}
{{- $author := .Params.author | default .Site.Params.author | default .Site.Title -}}
{{- $robots := .Params.robots | default "index, follow" -}}
{{- $canonical := .Permalink | default .RelPermalink -}}
<!-- Primary Meta Tags -->
<meta name="description" content="{{ $description }}">
<meta name="keywords" content="{{ $keywords }}">
<meta name="author" content="{{ $author }}">
<meta name="robots" content="{{ $robots }}">
<meta name="generator" content="Hugo {{ hugo.Version }}">
<!-- Canonical URL -->
<link rel="canonical" href="{{ $canonical }}">
<!-- Theme Color -->
<meta name="theme-color" content="{{ .Site.Params.seo.theme_color | default "#007bff" }}">
<meta name="msapplication-TileColor" content="{{ .Site.Params.seo.theme_color | default "#007bff" }}">
<!-- Additional SEO -->
<meta name="rating" content="general">
<meta name="language" content="{{ .Site.Language.Lang | default "en" }}">
<meta name="revisit-after" content="7 days">
<meta name="distribution" content="global">
<meta name="web_author" content="{{ .Site.Params.author | default .Site.Title }}">
<!-- Geo Tags (if location is specified) -->
{{ with .Site.Params.seo.geo }}
<meta name="geo.region" content="{{ .region }}">
<meta name="geo.placename" content="{{ .placename }}">
<meta name="geo.position" content="{{ .latitude }};{{ .longitude }}">
<meta name="ICBM" content="{{ .latitude }}, {{ .longitude }}">
{{ end }}
<!-- Dublin Core -->
<meta name="DC.Title" content="{{ .Title | default .Site.Title }}">
<meta name="DC.Creator" content="{{ $author }}">
<meta name="DC.Description" content="{{ $description }}">
<meta name="DC.Language" content="{{ .Site.Language.Lang | default "en" }}">

View File

@@ -0,0 +1,85 @@
{{- $title := .Title | default .Site.Title -}}
{{- $description := .Description | default .Summary | default .Site.Params.description | default .Site.Title -}}
{{- $image := .Params.image | default .Site.Params.seo.default_image | default "/assets/images/logo.png" -}}
{{- $image = $image | absURL -}}
{{- $url := .Permalink | default .RelPermalink | absURL -}}
{{- $siteName := .Site.Title -}}
{{- $logo := .Site.Params.seo.logo | default "/assets/images/logo.png" | absURL -}}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "{{ $siteName }}",
"description": "{{ $description }}",
"url": "{{ .Site.BaseURL }}",
"logo": {
"@type": "ImageObject",
"url": "{{ $logo }}"
}
}
</script>
{{ if .IsPage }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "{{ $title }}",
"description": "{{ $description }}",
"image": "{{ $image }}",
"url": "{{ $url }}",
"author": {
"@type": "Person",
"name": "{{ .Params.author | default .Site.Params.author | default .Site.Title }}"
},
"publisher": {
"@type": "Organization",
"name": "{{ $siteName }}",
"logo": {
"@type": "ImageObject",
"url": "{{ $logo }}"
}
}
}
</script>
{{ end }}
<!-- Breadcrumb Schema -->
{{ if and .IsPage .Section }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Accueil",
"item": "{{ .Site.BaseURL }}"
}
{{ if .Section }}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ .Section | humanize }}",
"item": "{{ .Site.BaseURL }}{{ .Section }}/"
}
,{
"@type": "ListItem",
"position": 3,
"name": "{{ $title }}",
"item": "{{ $url }}"
}
{{ else }}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ $title }}",
"item": "{{ $url }}"
}
{{ end }}
]
}
</script>
{{ end }}

View File

@@ -0,0 +1,27 @@
{{- $title := .Title | default .Site.Title -}}
{{- $description := .Description | default .Summary | default .Site.Params.description | default .Site.Title -}}
{{- $image := .Params.image | default .Site.Params.seo.default_image | default "/assets/images/twitter-default.jpg" -}}
{{- $image = $image | absURL -}}
{{- $siteName := .Site.Title -}}
{{- $twitter := .Site.Params.seo.twitter -}}
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $title }}">
<meta name="twitter:description" content="{{ $description }}">
<meta name="twitter:image" content="{{ $image }}">
<meta name="twitter:image:alt" content="{{ $title }}">
<!-- Twitter Site -->
{{ with $twitter.site }}
<meta name="twitter:site" content="{{ . }}">
{{ end }}
<!-- Twitter Creator -->
{{ with $twitter.creator }}
<meta name="twitter:creator" content="{{ . }}">
{{ else }}
{{ with .Params.author }}
<meta name="twitter:creator" content="{{ . }}">
{{ end }}
{{ end }}

510
package-lock.json generated
View File

@@ -10,6 +10,349 @@
"dependencies": {
"he": "^1.2.0",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"sass": "^1.90.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/data-uri-to-buffer": {
@@ -21,6 +364,20 @@
"node": ">= 12"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -44,6 +401,20 @@
"node": "^12.20 || >= 14.13"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -65,6 +436,72 @@
"he": "bin/he"
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -103,6 +540,79 @@
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sass": {
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@@ -6,12 +6,16 @@
"fetch-data": "node scripts/fetch-wordpress.js",
"generate-content": "node scripts/generate-content.js",
"prebuild": "npm run fetch-data && npm run generate-content",
"build": "hugo --minify",
"dev": "npm run fetch-data && npm run generate-content && hugo server -D",
"build": "npm run build:css && hugo --minify",
"build:css": "sass assets/css/scss:static/assets/css",
"dev": "npm run fetch-data && npm run generate-content && npm run build:css && hugo server -D",
"clean": "rm -rf data/wordpress content/posts public"
},
"dependencies": {
"he": "^1.2.0",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"sass": "^1.69.5"
}
}
}

View File

@@ -4,32 +4,32 @@ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch
const WORDPRESS_API = 'https://www.mistergeek.net/wp-json/wp/v2';
const OUTPUT_DIR = path.join(__dirname, '..', 'data', 'wordpress');
const HUGO_DATA_DIR = path.join(__dirname, '..', 'data');
async function fetchPosts(page = 1, perPage = 100) {
const response = await fetch(`${WORDPRESS_API}/posts?page=${page}&per_page=${perPage}&_embed`);
const posts = await response.json();
if (response.headers.get('x-wp-totalpages') > page) {
const nextPosts = await fetchPosts(page + 1, perPage);
return [...posts, ...nextPosts];
async function fetchAll(endpoint, perPage = 100) {
let page = 1;
let items = [];
while (true) {
const url = `${WORDPRESS_API}/${endpoint}?page=${page}&per_page=${perPage}&_embed`;
const response = await fetch(url);
// If endpoint does not support paging or returns empty, break
if (!response.ok) {
// 400 often means "page out of range" for WP; stop paging
if (response.status === 400) break;
throw new Error(`Failed to fetch ${endpoint} (page ${page}): ${response.status} ${response.statusText}`);
}
const batch = await response.json();
items = items.concat(batch);
const totalPages = parseInt(response.headers.get('x-wp-totalpages') || '1', 10);
if (page >= totalPages) break;
page++;
}
return posts;
}
async function fetchCategories() {
const response = await fetch(`${WORDPRESS_API}/categories?per_page=100`);
return response.json();
}
async function fetchTags() {
const response = await fetch(`${WORDPRESS_API}/tags?per_page=100`);
return response.json();
}
async function fetchAuthors() {
const response = await fetch(`${WORDPRESS_API}/users?per_page=100`);
return response.json();
return items;
}
async function generateData() {
@@ -39,21 +39,45 @@ async function generateData() {
}
console.log('Fetching WordPress data...');
const [posts, categories, tags, authors] = await Promise.all([
fetchPosts(),
fetchCategories(),
fetchTags(),
fetchAuthors()
// Fetch all relevant endpoints concurrently (pages + posts + taxonomies + users)
const [
posts,
pages,
categories,
tags,
authors
] = await Promise.all([
fetchAll('posts'),
fetchAll('pages'),
fetchAll('categories'),
fetchAll('tags'),
fetchAll('users')
]);
// Filter pages to only include published pages
const publishedPages = pages.filter(page => page.status === 'publish');
// Create navigation data from published pages
const navigationData = publishedPages.map(page => ({
id: page.id,
title: page.title?.rendered || page.slug,
slug: page.slug,
link: page.link,
date: page.date,
modified: page.modified
}));
// Save data as JSON files
fs.writeFileSync(path.join(OUTPUT_DIR, 'posts.json'), JSON.stringify(posts, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'pages.json'), JSON.stringify(publishedPages, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'categories.json'), JSON.stringify(categories, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'tags.json'), JSON.stringify(tags, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'authors.json'), JSON.stringify(authors, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'navigation.json'), JSON.stringify(navigationData, null, 2));
console.log(`✅ Fetched ${posts.length} posts, ${categories.length} categories, ${tags.length} tags, ${authors.length} authors`);
console.log(`✅ Fetched ${posts.length} posts, ${publishedPages.length} pages, ${categories.length} categories, ${tags.length} tags, ${authors.length} authors`);
console.log(`✅ Generated navigation data with ${navigationData.length} items`);
}
generateData().catch(console.error);

View File

@@ -4,16 +4,23 @@ const he = require('he');
const DATA_DIR = path.join(__dirname, '..', 'data', 'wordpress');
const CONTENT_DIR = path.join(__dirname, '..', 'content');
const PAGES_DIR = path.join(CONTENT_DIR, 'pages');
function generateContent() {
const posts = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'posts.json'), 'utf8'));
// Ensure content directory exists
const pages = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'pages.json'), 'utf8'));
// Ensure content directories exist
if (!fs.existsSync(CONTENT_DIR)) {
fs.mkdirSync(CONTENT_DIR, { recursive: true });
}
if (!fs.existsSync(PAGES_DIR)) {
fs.mkdirSync(PAGES_DIR, { recursive: true });
}
posts.forEach(post => {
// Process posts - only include published posts
posts.filter(post => post.status === 'publish').forEach(post => {
const slug = post.slug;
const date = new Date(post.date);
const year = date.getFullYear();
@@ -33,7 +40,7 @@ function generateContent() {
const frontmatter = {
title: he.decode(post.title.rendered),
date: post.date,
draft: post.status !== 'publish',
draft: false,
slug: slug,
wordpress_id: post.id,
excerpt: he.decode(post.excerpt.rendered.replace(/<[^>]*>/g, '')),
@@ -47,21 +54,27 @@ function generateContent() {
// Decode HTML entities in the content and clean up HTML tags
let contentHtml = he.decode(post.content.rendered);
// Convert absolute URLs in a href to relative URLs
// Convert absolute URLs in a href to relative URLs (only for wp.mistergeek.net)
contentHtml = contentHtml.replace(/<a\s+[^>]*href="([^"]+)"[^>]*>/g, (match, href) => {
// Check if the href is an absolute URL (starts with http:// or https://)
// Check if the href is an absolute URL containing wp.mistergeek.net
if (href.startsWith('http://') || href.startsWith('https://')) {
// Extract the path part of the URL
const url = new URL(href);
// Return the modified a tag with relative URL
return match.replace(href, url.pathname);
try {
const url = new URL(href);
if (url.hostname === 'wp.mistergeek.net' || url.hostname === 'www.wp.mistergeek.net') {
// Only convert wp.mistergeek.net URLs to relative paths
return match.replace(href, url.pathname);
}
} catch (error) {
// If URL parsing fails, return the original href
console.warn('Failed to parse URL:', href, error);
}
}
return match;
});
contentHtml = contentHtml
.replace(/<p>\s*<\/p>/g, '') // Remove empty paragraphs
.replace(/<\/p>\s*<p>/g, '\n\n') // Replace paragraph breaks with newlines
.replace(/<\/p>\s*<p>/g, '\n\n'); // Replace paragraph breaks with newlines
const content = `---
${Object.entries(frontmatter)
@@ -73,7 +86,67 @@ ${contentHtml.trim()}`;
fs.writeFileSync(path.join(contentDir, 'index.md'), content);
});
console.log(`✅ Generated ${posts.length} content files`);
// Process pages - only include published pages
pages.filter(page => page.status === 'publish').forEach(page => {
const slug = page.slug;
const contentDir = path.join(PAGES_DIR, slug);
if (!fs.existsSync(contentDir)) {
fs.mkdirSync(contentDir, { recursive: true });
}
const frontmatter = {
title: he.decode(page.title.rendered),
slug: slug,
type: "pages",
layout: "single",
wordpress_id: page.id,
date: page.date,
modified: page.modified,
draft: false,
aliases: [`/${slug}/`]
};
// Decode HTML entities in the content and clean up HTML tags
let contentHtml = he.decode(page.content.rendered);
// Convert absolute URLs in a href to relative URLs (only for wp.mistergeek.net)
contentHtml = contentHtml.replace(/<a\s+[^>]*href="([^"]+)"[^>]*>/g, (match, href) => {
// Check if the href is an absolute URL containing wp.mistergeek.net
if (href.startsWith('http://') || href.startsWith('https://')) {
try {
const url = new URL(href);
if (url.hostname === 'wp.mistergeek.net' || url.hostname === 'www.wp.mistergeek.net') {
// Only convert wp.mistergeek.net URLs to relative paths
return match.replace(href, url.pathname);
}
} catch (error) {
// If URL parsing fails, return the original href
console.warn('Failed to parse URL:', href, error);
}
}
return match;
});
contentHtml = contentHtml
.replace(/<p>\s*<\/p>/g, '') // Remove empty paragraphs
.replace(/<\/p>\s*<p>/g, '\n\n'); // Replace paragraph breaks with newlines
const content = `---
${Object.entries(frontmatter)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join('\n')}
---
${contentHtml.trim()}`;
fs.writeFileSync(path.join(contentDir, 'index.md'), content);
});
const publishedPosts = posts.filter(post => post.status === 'publish');
const publishedPages = pages.filter(page => page.status === 'publish');
console.log(`✅ Generated ${publishedPosts.length} content files`);
console.log(`✅ Generated ${publishedPages.length} page files`);
}
generateContent();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

39
test-table.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Table Test</title>
<link rel="stylesheet" href="assets/css/scss/theme.scss">
</head>
<body>
<figure class="wp-block-table">
<table class="has-fixed-layout">
<thead>
<tr>
<th>TECHNOLOGIE</th>
<th>DÉBIT MAXIMUM THÉORIQUE (Mégabit/s)</th>
<th>REMARQUE(S)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>FTTH (Fibre jusquà labonné)</strong></td>
<td>Jusquà 10 000 Mégabit/s</td>
<td>Débit symétrique, très haute performance.</td>
</tr>
<tr>
<td><strong>FTTLa (Fibre jusquau dernier amplificateur)</strong></td>
<td>Jusquà 1000 Mégabit/s</td>
<td>Débit variable selon la distance au point de raccordement.</td>
</tr>
<tr>
<td><strong>Boucle Locale Radio</strong></td>
<td>Jusquà 100 Mégabit/s</td>
<td>Dépend de la qualité du signal et de lenvironnement.</td>
</tr>
</tbody>
</table>
</figure>
</body>
</html>

183
yarn.lock
View File

@@ -2,11 +2,119 @@
# yarn lockfile v1
"@parcel/watcher-android-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz"
integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==
"@parcel/watcher-darwin-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz"
integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
"@parcel/watcher-darwin-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz"
integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==
"@parcel/watcher-freebsd-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz"
integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==
"@parcel/watcher-linux-arm-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz"
integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==
"@parcel/watcher-linux-arm-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz"
integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==
"@parcel/watcher-linux-arm64-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz"
integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==
"@parcel/watcher-linux-arm64-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz"
integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==
"@parcel/watcher-linux-x64-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz"
integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==
"@parcel/watcher-linux-x64-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz"
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==
"@parcel/watcher-win32-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz"
integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==
"@parcel/watcher-win32-ia32@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz"
integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==
"@parcel/watcher-win32-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz"
integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==
"@parcel/watcher@^2.4.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==
dependencies:
detect-libc "^1.0.3"
is-glob "^4.0.3"
micromatch "^4.0.5"
node-addon-api "^7.0.0"
optionalDependencies:
"@parcel/watcher-android-arm64" "2.5.1"
"@parcel/watcher-darwin-arm64" "2.5.1"
"@parcel/watcher-darwin-x64" "2.5.1"
"@parcel/watcher-freebsd-x64" "2.5.1"
"@parcel/watcher-linux-arm-glibc" "2.5.1"
"@parcel/watcher-linux-arm-musl" "2.5.1"
"@parcel/watcher-linux-arm64-glibc" "2.5.1"
"@parcel/watcher-linux-arm64-musl" "2.5.1"
"@parcel/watcher-linux-x64-glibc" "2.5.1"
"@parcel/watcher-linux-x64-musl" "2.5.1"
"@parcel/watcher-win32-arm64" "2.5.1"
"@parcel/watcher-win32-ia32" "2.5.1"
"@parcel/watcher-win32-x64" "2.5.1"
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
chokidar@^4.0.0:
version "4.0.3"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz"
integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
dependencies:
readdirp "^4.0.1"
data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz"
@@ -15,6 +123,13 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz"
@@ -27,6 +142,41 @@ he@^1.2.0:
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
immutable@^5.0.2:
version "5.1.3"
resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz"
integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
micromatch@^4.0.5:
version "4.0.8"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
node-addon-api@^7.0.0:
version "7.1.1"
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz"
@@ -41,6 +191,39 @@ node-fetch@^3.3.2:
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
readdirp@^4.0.1:
version "4.1.2"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
sass@^1.90.0:
version "1.90.0"
resolved "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz"
integrity sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==
dependencies:
chokidar "^4.0.0"
immutable "^5.0.2"
source-map-js ">=0.6.2 <2.0.0"
optionalDependencies:
"@parcel/watcher" "^2.4.1"
"source-map-js@>=0.6.2 <2.0.0":
version "1.2.1"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
web-streams-polyfill@^3.0.3:
version "3.3.3"
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"