7 Commits

Author SHA1 Message Date
kbe
98f639b913 prepare production 2025-08-19 19:31:12 +02:00
kbe
40e9660d80 Prepare docker workflow 2025-08-19 19:10:43 +02:00
kbe
ef6876a0d3 seo improvement with titles h1/h4 2025-08-19 17:17:09 +02:00
kbe
851547b87d chore: update site layout and styles 2025-08-19 16:54:09 +02:00
kbe
bd803eda00 Make use of relative URL 2025-08-19 16:11:18 +02:00
kbe
ac76ba32f6 Display categories 2025-08-19 16:06:28 +02:00
kbe
c84ca1d538 Some minor fixes 2025-08-19 15:23:17 +02:00
26 changed files with 759 additions and 227 deletions

38
.dockerignore Normal file
View File

@@ -0,0 +1,38 @@
# Hugo build artifacts
.hugo_build.lock
hugo_stats.json
public/
resources/
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Development files
.editorconfig
.git/
.gitignore
*.md
docs/
# OS files
.DS_Store
Thumbs.db
# Backup files
*.backup
*.bak
*.tmp
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Build artifacts
dist/
build/

43
.editorconfig Normal file
View File

@@ -0,0 +1,43 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 4 space indentation (no tabs)
[*.{js,jsx,ts,tsx,html,css,scss,md}]
indent_style = space
indent_size = 4
# Set charset
[*.{js,jsx,ts,tsx,html,css,scss,md}]
charset = utf-8
# For Go files
[*.go]
indent_style = space
indent_size = 4
# For Python files
[*.py]
indent_style = space
indent_size = 4
# For JSON files
[*.json]
indent_style = space
indent_size = 2
# For YAML files
[*.{yaml,yml}]
indent_style = space
indent_size = 2
# For TOML files
[*.toml]
indent_style = space
indent_size = 2

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# Use a Node.js LTS Alpine image as the base
FROM hugomods/hugo:nightly
# Install Node.js and npm (needed for fetch-wordpress.js and sass)
RUN apk add --no-cache nodejs npm
# Install Sass (Dart Sass) globally
RUN npm install -g sass
# Set the working directory inside the container
WORKDIR /app
# Copy the entire project into the container
COPY . .
# Make the build script executable (if not already)
RUN chmod +x scripts/build.sh
# Install Node.js dependencies if any (e.g., for fetch-wordpress.js)
# Assuming package.json exists and has dependencies
RUN if [ -f package.json ]; then npm install; fi
# Ensure /usr/local/bin is in PATH for the CMD
ENV PATH="/usr/local/bin:$PATH"
# Command to run the build script when the container starts
CMD ["./scripts/build.sh"]

View File

@@ -0,0 +1,51 @@
.author-card-item {
margin-bottom: 2rem;
border: 1px solid #eee;
padding: 1rem;
display: flex;
align-items: center;
.author-card-image {
flex: 0 0 33.333333%;
max-width: 33.333333%;
padding-right: 1rem;
img {
border-radius: 50%;
width: 100%;
height: auto;
display: block;
}
}
.author-card-info {
flex: 0 0 66.666667%;
max-width: 66.666667%;
padding-left: 1rem;
.author-card-name {
font-size: 1.25rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.author-card-title {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
.author-card-buttons {
.author-button-link {
display: inline-flex;
align-items: center;
color: #007bff;
text-decoration: none;
svg {
margin-left: 0.5rem;
}
}
}
}
}

View File

@@ -57,6 +57,7 @@
@import "components/preloader"; @import "components/preloader";
@import "components/section"; @import "components/section";
@import "components/_ez-toc"; @import "components/_ez-toc";
@import "components/author-card";
// //

46
docker-compose.yml Normal file
View File

@@ -0,0 +1,46 @@
networks:
default:
web:
external: true
services:
nginx:
image: nginx:alpine
volumes:
- ./public:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
labels:
# Explicitly tell Traefik to expose this container
- traefik.enable=true
- traefik.docker.network=web
# HTTPS
- traefik.http.services.mg-hugo-service-secure.loadbalancer.server.port=80
- traefik.http.routers.mg-hugo-secure.service=mg-hugo-service-secure
- traefik.http.routers.mg-hugo-secure.entrypoints=websecure
- traefik.http.routers.mg-hugo-secure.tls.certresolver=le
- traefik.http.routers.mg-hugo-secure.rule=Host(`mistergeek.fr`, `www.mistergeek.fr`, `mistergeek.net`, `www.mistergeek.net`, `agence-webside.fr`, `www.agence-webside.fr`)
- traefik.http.middlewares.mg-hugo-secure-cache.compress=true
# HTTP
- traefik.http.services.mg-hugo-service-insecure.loadbalancer.server.port=80
- traefik.http.routers.mg-hugo-insecure.service=mg-hugo-service-insecure
- traefik.http.routers.mg-hugo-insecure.entrypoints=web
- traefik.http.routers.mg-hugo-insecure.rule=Host(`mistergeek.fr`, `www.mistergeek.fr`, `mistergeek.net`, `www.mistergeek.net`, `agence-webside.fr`, `www.agence-webside.fr`)
- traefik.http.middlewares.mg-hugo-insecure-cache.compress=true
- traefik.http.routers.traefik.tls=true
- traefik.http.routers.traefik.tls.certresolver=le
# GZIP
- traefik.http.routers.traefik.middlewares=traefik-compress
- traefik.http.middlewares.traefik-compress.compress=true
networks:
- web
builder:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
networks:
- default
# command: ls -l

View File

@@ -7,15 +7,8 @@ ignoreLogs = ["warning-goldmark-raw-html"]
# [permalinks] # [permalinks]
# posts = "/:section/:slug/" # posts = "/:section/:slug/"
[languages] # [taxonomies]
[languages.fr] # category = "categories"
languageCode = 'fr-FR'
languageDirection = 'ltr'
languageName = 'French'
weight = 1
[taxonomies]
category = "categories"
[markup.goldmark.renderer] [markup.goldmark.renderer]
unsafe = true unsafe = true

View File

@@ -1,10 +1,9 @@
{{ define "main" }} {{ define "main" }}
{{ $defaultCategory := "General" }}
{{ if .Site.Params.defaultCategory }}{{ $defaultCategory = .Site.Params.defaultCategory }}{{ end }}
<!-- layouts/_default/list.html -->
<div class="section-sm bg-gray-lighter"> <div class="section-sm bg-gray-lighter">
<div class="container text-center"> <div class="container text-center">
<h3 class="font-family-playfair">{{ .Title }}</h3> <h1 class="font-family-playfair">{{ .Site.Title }}</h1>
</div><!-- end container --> </div><!-- end container -->
</div> </div>
@@ -30,31 +29,14 @@
</div> </div>
<div class="mt-4"> <div class="mt-4">
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<div class="d-inline-flex"> <!-- Display category -->
{{ if .Params.categories }} {{ partial "categories-first.html" . }}
{{ range $index, $category := .Params.categories }}
{{ if $index }}, {{ end }}
{{ if and (eq (printf "%T" $category) "string") }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category | urlize }}">{{ $category }}</a>
{{ else if and (eq (printf "%T" $category) "map") }}
{{ if $category.name }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category.name | urlize }}">{{ $category.name }}</a>
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
{{ end }}
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
</div>
<div class="d-inline-flex"> <div class="d-inline-flex">
<span class="font-small">{{ .Date.Format "02/07/2006" }}</span> <span class="font-small">{{ .Date.Format "02/07/2006" }}</span>
</div> </div>
</div> </div>
<h4><a class="text-link-1" href="{{ .RelPermalink }}">{{ .Title }}</a></h4> <h2><a class="text-link-1" href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
{{ if .Params.excerpt }} {{ if .Params.excerpt }}
<p>{{ .Params.excerpt }}</p> <p>{{ .Params.excerpt }}</p>
{{ else if .Summary }} {{ else if .Summary }}
@@ -78,3 +60,4 @@
</div> </div>
<!-- end Blog section --> <!-- end Blog section -->
{{ end }} {{ end }}

View File

@@ -6,14 +6,14 @@
<h1 class="fw-normal">{{ .Title }}</h1> <h1 class="fw-normal">{{ .Title }}</h1>
<ul class="list-inline-dash"> <ul class="list-inline-dash">
{{ if .Params.author }} {{ if .Params.author }}
<li><a href="/author/{{ .Params.author | urlize }}">{{ .Params.author }}</a></li> <li>Par <a href="/author/{{ .Params.author | anchorize }}">{{ .Params.author }}</a></li>
{{ end }} {{ end }}
{{ with .Params.categories }} {{ with .Params.categories }}
{{ range . }} {{ range . }}
<li><a href="/categories/{{ . | urlize }}">{{ . }}</a></li> <li>dans <a href="/{{ . | anchorize }}">{{ . }}</a></li>
{{ end }} {{ end }}
{{ end }} {{ end }}
<li><a href="#">{{ .Date.Format "02/01/2006" }}</a></li> <li> le {{ .Date.Format "02/01/2006" }}</li>
</ul> </ul>
</div> </div>
</div><!-- end row --> </div><!-- end row -->
@@ -44,6 +44,7 @@
</div> </div>
<!-- end Post Content --> <!-- end Post Content -->
{{/*
<!-- Tags and Share --> <!-- Tags and Share -->
<div class="section-xs border-top"> <div class="section-xs border-top">
<div class="container"> <div class="container">
@@ -53,7 +54,7 @@
<h6 class="font-small fw-medium uppercase">Tags</h6> <h6 class="font-small fw-medium uppercase">Tags</h6>
<ul class="list-inline-sm"> <ul class="list-inline-sm">
{{ range .Params.tags }} {{ range .Params.tags }}
<li><a href="/tags/{{ . | urlize }}">{{ . }}</a></li> <li><a href="/tags/{{ . | anchorize }}">{{ . }}</a></li>
{{ end }} {{ end }}
</ul> </ul>
</div> </div>
@@ -69,6 +70,7 @@
</div><!-- end row --> </div><!-- end row -->
</div><!-- end container --> </div><!-- end container -->
</div> </div>
*/}}
<!-- Comments section --> <!-- Comments section -->
{{ if .Site.Params.comments.enable }} {{ if .Site.Params.comments.enable }}

View File

@@ -0,0 +1,161 @@
{{ define "main" }}
<!-- Page Header -->
<div class="section-sm bg-gray-lighter">
<div class="container text-center">
<h3 class="font-family-playfair">Authors</h3>
<p class="mt-2">Discover all our talented authors and their contributions</p>
</div>
</div>
<!-- Authors Section -->
<div class="section">
<div class="container">
<div class="row g-4">
{{ $authors := slice }}
{{ $authorMap := dict }}
<!-- Collect all unique authors -->
{{ range .Site.RegularPages }}
{{ if .Params.author }}
{{ $authorName := .Params.author }}
{{ $authorSlug := .Params.author_slug | default ($authorName | urlize) }}
<!-- Check if author already exists -->
{{ $existing := false }}
{{ range $authors }}
{{ if eq .name $authorName }}
{{ $existing = true }}
{{ end }}
{{ end }}
<!-- Add new author if not exists -->
{{ if not $existing }}
{{ $author := dict
"name" $authorName
"slug" $authorSlug
"bio" .Params.author_bio
"photo" .Params.author_photo
"website" .Params.author_website
"twitter" .Params.author_twitter
"linkedin" .Params.author_linkedin
"posts" (slice)
}}
{{ $authors = $authors | append $author }}
{{ $authorMap = merge $authorMap (dict $authorName $author) }}
{{ end }}
<!-- Add post to author's posts -->
{{ if $author := index $authorMap $authorName }}
{{ $post := dict
"title" .Title
"permalink" .Permalink
}}
{{ $author := merge $author (dict "posts" ($author.posts | append $post)) }}
{{ $authorMap = merge $authorMap (dict $authorName $author) }}
{{ end }}
{{ end }}
{{ end }}
<!-- Display authors -->
{{ if gt (len $authors) 0 }}
{{ range $authors }}
<div class="col-12 col-md-6 col-lg-4">
<div class="team-box team-box-style-2">
<div class="team-box-img">
{{ if .photo }}
<img src="{{ .photo }}" alt="{{ .name }}">
{{ else }}
<img src="/assets/images/avatar-placeholder.jpg" alt="{{ .name }}">
{{ end }}
<div class="team-box-content">
<h5 class="font-family-poppins">{{ .name }}</h5>
{{ if .bio }}
<p class="font-small">{{ .bio | truncate 120 }}</p>
{{ end }}
<div class="team-box-social">
{{ if .website }}
<a href="{{ .website }}" target="_blank" rel="noopener noreferrer">
<i class="fas fa-globe"></i>
</a>
{{ end }}
{{ if .twitter }}
<a href="https://twitter.com/{{ .twitter }}" target="_blank" rel="noopener noreferrer">
<i class="fab fa-twitter"></i>
</a>
{{ end }}
</div>
{{ if .linkedin }}
<a href="https://linkedin.com/in/{{ .linkedin }}" target="_blank" rel="noopener noreferrer">
<i class="fab fa-linkedin-in"></i>
</a>
{{ end }}
</div>
<div class="mt-3">
<a href="/author/{{ .slug }}" class="button-text-1">
{{ len .posts }} {{ if eq (len .posts) 1 }}Article{{ else }}Articles{{ end }}
</a>
</div>
</div>
</div>
</div>
{{ end }}
{{ else }}
<div class="col-12 text-center py-5">
<h4>No authors found</h4>
<p>No authors have been added yet. Check back soon!</p>
</div>
{{ end }}
</div>
</div>
</div>
<!-- Additional Author Information Section -->
{{ if gt (len $authors) 0 }}
<div class="section bg-gray-lighter">
<div class="container">
<div class="row">
<div class="col-12 text-center">
<h4 class="font-family-playfair mb-4">Our Authors</h4>
<p class="max-width-600 mx-auto">
Meet the talented writers and contributors who bring you insightful content across various topics.
Each author brings their unique perspective and expertise to create valuable content for our readers.
</p>
</div>
</div>
<div class="row mt-5">
<div class="col-12">
<div class="row g-3">
{{ range $authors }}
<div class="col-12 col-md-6">
<div class="d-flex align-items-center p-3 bg-white rounded">
<div class="flex-shrink-0">
{{ if .photo }}
<img src="{{ .photo }}" alt="{{ .name }}" class="rounded-circle" width="60" height="60">
{{ else }}
<div class="bg-gray rounded-circle d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-user"></i>
</div>
{{ end }}
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">{{ .name }}</h6>
<small class="text-muted">{{ len .posts }} {{ if eq (len .posts) 1 }}publication{{ else }}publications{{ end }}</small>
</div>
<div class="flex-shrink-0">
<a href="/author/{{ .slug }}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-arrow-right"></i>
</a>
</div>
</div>
</div>
{{ end }}
</div>
</div>
</div>
</div>
</div>
{{ end }}
{{ end }}

49
layouts/author/list.html Normal file
View File

@@ -0,0 +1,49 @@
{{ define "main" }}
{{ $authors := site.Data.wordpress.authors }}
<div class="section-sm bg-gray-lighter">
<div class="container text-center">
<h3 class="font-family-playfair">{{ .Title }}</h3>
<p class="mt-2">Liste de tous les auteurs</p>
</div><!-- end container -->
</div>
<div class="section">
<div class="container">
<div class="author-list">
<div class="row">
{{ range $author := $authors }}
<div class="col-md-6 mb-4">
<article class="author-card-item h-100 card" data-author-name="{{ $author.name }}">
<div class="row g-0 align-items-center">
<div class="author-card-image col-md-4 p-2">
{{ with index $author.avatar_urls "96" }}
<img decoding="async" src="{{ . }}" alt="{{ $author.name }}" class="author-image img-fluid" loading="lazy">
{{ else }}
<img decoding="async" src="/assets/images/img-avatar-md@2x.jpg" alt="{{ $author.name }}" class="author-image img-fluid" loading="lazy">
{{ end }}
</div>
<div class="author-card-info col-md-8">
<div class="card-body">
<h5 class="card-title author-card-name">{{ $author.name }}</h5>
{{ with $author.description }}
<p class="card-text author-card-title">
<small class="text-muted author-card-title-text">{{ . }}</small>
</p>
{{ end }}
<div class="author-card-buttons mt-3">
<div class="author-card-button">
<a href="/author/{{ $author.slug | anchorize }}" class="author-button-link" aria-label="Voir les articles de {{ $author.name }}">Voir les articles<svg class="uikit-icon" style="width: 1em;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="var(--c-svg, currentColor)" d="M7.5 4.5V6h9.442L4.5 18.442 5.558 19.5 18 7.058V16.5h1.5v-12z"></path></svg></a>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
{{ end }}
</div>
</div>
</div><!-- end container -->
</div>
{{ end }}

View File

@@ -0,0 +1,34 @@
{{ define "main" }}
{{ $defaultCategory := "General" }}
{{ if .Site.Params.defaultCategory }}{{ $defaultCategory = .Site.Params.defaultCategory }}{{ end }}
<div class="section-sm bg-gray-lighter">
<div class="container text-center">
<h1 class="font-family-playfair">{{ .Title }}</h1>
<p class="mt-3">Cette page répertorie toutes les catégories de notre site, offrant une vue d'ensemble structurée de notre contenu.</p>
</div><!-- end container -->
</div>
<!-- Blog section -->
<div class="section">
<div class="container">
<div class="row g-4">
<div class="col-12 col-sm-10 offset-sm-1 col-md-8 offset-md-2">
{{ range $.Site.Data.wordpress.categories }}
<div class="mb-5">
<div class="mt-4">
<h2><a class="text-link-1" href="{{ .link | relURL }}">{{ .name }}</a></h2>
<p>{{ .description | safeHTML }}</p>
<div class="mt-3">
<a class="button-text-1" href="{{ .link | relURL }}">Voir la catégorie</a>
</div>
</div>
</div>
{{ if not .IsLast }}<hr class="my-5">{{ end }}
{{ end }}
</div>
</div><!-- end row -->
</div><!-- end container -->
</div>
<!-- end Blog section -->
{{ end }}

View File

@@ -1,9 +1,10 @@
{{ define "main" }} {{ define "main" }}
{{ $defaultCategory := "General" }}
{{ if .Site.Params.defaultCategory }}{{ $defaultCategory = .Site.Params.defaultCategory }}{{ end }} <!-- layouts/index.html -->
<div class="section-sm bg-gray-lighter"> <div class="section-sm bg-gray-lighter">
<div class="container text-center"> <div class="container text-center">
<h3 class="font-family-playfair">{{ .Site.Title }} blog.</h3> <h1 class="font-family-playfair">{{ .Site.Title }}</h1>
<p class="mt-3">{{ .Site.Params.seo.description }}</p>
</div><!-- end container --> </div><!-- end container -->
</div> </div>
@@ -27,31 +28,14 @@
</div> </div>
<div class="mt-4"> <div class="mt-4">
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<div class="d-inline-flex"> <!-- Display category -->
{{ if .Params.categories }} {{ partial "categories-first.html" . }}
{{ range $index, $category := .Params.categories }}
{{ if $index }}, {{ end }}
{{ if and (eq (printf "%T" $category) "string") }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category | urlize }}">{{ $category }}</a>
{{ else if and (eq (printf "%T" $category) "map") }}
{{ if $category.name }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category.name | urlize }}">{{ $category.name }}</a>
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
{{ end }}
{{ else }}
<a class="font-family-poppins font-small fw-medium uppercase" href="#">{{ $defaultCategory }}</a>
{{ end }}
</div>
<div class="d-inline-flex"> <div class="d-inline-flex">
<span class="font-small">{{ .Date.Format "02/07/2006" }}</span> <span class="font-small">{{ .Date.Format "02/07/2006" }}</span>
</div> </div>
</div> </div>
<h4><a class="text-link-1" href="{{ .Permalink }}">{{ .Title }}</a></h4> <h2><a class="text-link-1" href="{{ .Permalink }}">{{ .Title }}</a></h2>
{{ if .Params.excerpt }} {{ if .Params.excerpt }}
<p>{{ .Params.excerpt }}</p> <p>{{ .Params.excerpt }}</p>
{{ else if .Summary }} {{ else if .Summary }}

View File

@@ -5,8 +5,8 @@
<div class="section-sm bg-gray-lighter"> <div class="section-sm bg-gray-lighter">
<div class="container text-center"> <div class="container text-center">
<h3 class="font-family-playfair">Articles by {{ $authorName }}</h3> <h1 class="font-family-playfair">{{ .Title }}</h1>
<p class="mt-2">Tous les articles écrits par {{ $authorName }}</p> <p class="mt-2">Retrouvez toutes les pages utiles du site ici.</p>
</div><!-- end container --> </div><!-- end container -->
</div> </div>
@@ -23,36 +23,8 @@
{{ range $paginator.Pages }} {{ range $paginator.Pages }}
<!-- Blog Post box --> <!-- Blog Post box -->
<div class="mb-5"> <div class="mb-5">
<div class="img-link-box">
<a href="{{ .RelPermalink }}">
{{ if .Params.featured_image }}
<img src="{{ .Params.featured_image }}" alt="{{ .Title }}">
{{ else }}
<img src="/assets/images/col-1.jpg" alt="{{ .Title }}">
{{ end }}
</a>
</div>
<div class="mt-4"> <div class="mt-4">
<div class="d-flex justify-content-between mb-2"> <h2><a class="text-link-1" href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
<div class="d-inline-flex">
{{ if .Params.categories }}
{{ range $index, $category := .Params.categories }}
{{ if $index }}, {{ end }}
{{ if and (eq (printf "%T" $category) "string") }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category | urlize }}">{{ $category }}</a>
{{ else if and (eq (printf "%T" $category) "map") }}
{{ if $category.name }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/categories/{{ $category.name | urlize }}">{{ $category.name }}</a>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</div>
<div class="d-inline-flex">
<span class="font-small">{{ .Date.Format "02/07/2006" }}</span>
</div>
</div>
<h4><a class="text-link-1" href="{{ .RelPermalink }}">{{ .Title }}</a></h4>
{{ if .Params.excerpt }} {{ if .Params.excerpt }}
<p>{{ .Params.excerpt }}</p> <p>{{ .Params.excerpt }}</p>
{{ else if .Summary }} {{ else if .Summary }}

View File

@@ -22,7 +22,7 @@
{{ if .Params.categories }} {{ if .Params.categories }}
{{ $category = index .Params.categories 0 }} {{ $category = index .Params.categories 0 }}
{{ $categorySlug = urlize $category }} {{ $categorySlug = anchorize $category }}
{{ $categoryUrl = printf "/categories/%s" $categorySlug }} {{ $categoryUrl = printf "/categories/%s" $categorySlug }}
{{ else if .Section }} {{ else if .Section }}
{{ $category = humanize .Section }} {{ $category = humanize .Section }}
@@ -63,7 +63,7 @@
"@type": "ListItem", "@type": "ListItem",
"position": 2, "position": 2,
"name": "{{ if .Params.categories }}{{ index .Params.categories 0 }}{{ else }}{{ humanize .Section }}{{ end }}", "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 }}" "item": "{{ if .Params.categories }}{{ printf "%s/categories/%s" (absLangURL "") (anchorize (index .Params.categories 0)) }}{{ else }}{{ printf "%s/%s" (absLangURL "") .Section }}{{ end }}"
}{{ end }} }{{ end }}
] ]
} }

View File

@@ -0,0 +1,13 @@
<div class="d-inline-flex">
{{ if .Params.categories }}
{{ with index .Params.categories 0 }}
{{ if and (eq (printf "%T" .) "string") }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/{{ . | anchorize }}">{{ . }}</a>
{{ else if and (eq (printf "%T" .) "map") }}
{{ if .name }}
<a class="font-family-poppins font-small fw-medium uppercase" href="/{{ .name | anchorize }}">{{ .name }}</a>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</div>

View File

@@ -13,7 +13,7 @@
{{ if .Site.Data.wordpress }} {{ if .Site.Data.wordpress }}
{{ range $index, $element := .Site.Data.wordpress.navigation }} {{ range $index, $element := .Site.Data.wordpress.navigation }}
<li> <li>
<a href="/{{ $element.slug }}">{{ $element.title }}</a> <a href="/pages/{{ $element.slug }}">{{ $element.title }}</a>
</li> </li>
{{ end }} {{ end }}
{{ end }} {{ end }}
@@ -27,14 +27,17 @@
{{ if .Site.Data.wordpress }} {{ if .Site.Data.wordpress }}
{{ $count := 0 }} {{ $count := 0 }}
{{ range $index, $element := .Site.Data.wordpress.categories }} {{ range $index, $element := .Site.Data.wordpress.categories }}
{{ if ne $element.name "Featured" }}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/categories/{{ $element.slug }}">{{ $element.name }}</a> <a class="nav-link" href="/{{ $element.slug }}">{{ $element.name }}</a>
</li> </li>
{{ end }} {{ end }}
{{ end }} {{ end }}
{{ end }}
</ul> </ul>
</div> </div>
{{/*
<div class="col-6 col-sm-6 col-lg-3"> <div class="col-6 col-sm-6 col-lg-3">
<h6 class="font-small fw-medium uppercase">Contact Info</h6> <h6 class="font-small fw-medium uppercase">Contact Info</h6>
<ul class="list-unstyled"> <ul class="list-unstyled">
@@ -43,6 +46,8 @@
<li>+(123) 456 789 01</li> <li>+(123) 456 789 01</li>
</ul> </ul>
</div> </div>
*/}}
</div><!-- end row --> </div><!-- end row -->
</div><!-- end container --> </div><!-- end container -->
</div> </div>
@@ -54,10 +59,10 @@
</div> </div>
<div class="col-12 col-md-6 text-center text-md-end"> <div class="col-12 col-md-6 text-center text-md-end">
<ul class="list-inline-sm"> <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-facebook" href="https://www.facebook.com/mistergeekfrance"><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-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-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> <!-- <li><a class="button-circle button-circle-sm button-circle-social-instagram" href="#"><i class="bi bi-instagram"></i></a></li> -->
</ul> </ul>
</div> </div>
</div><!-- end row --> </div><!-- end row -->

View File

@@ -18,7 +18,7 @@
{{ continue }} {{ continue }}
{{ else if lt $count 5 }} {{ else if lt $count 5 }}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/categories/{{ $element.slug }}">{{ $element.name }}</a> <a class="nav-link" href="/{{ $element.slug }}">{{ $element.name }}</a>
</li> </li>
{{ $count = add $count 1 }} {{ $count = add $count 1 }}
{{ else }} {{ else }}

42
nginx.conf Normal file
View File

@@ -0,0 +1,42 @@
server {
listen 80;
server_name mistergeek.fr mistergeek.net agence-webside.fr;
return 301 https://www.mistergeek.net$request_uri;
}
server {
listen 80;
server_name www.mistergeek.net;
root /usr/share/nginx/html;
index index.html index.htm;
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Main location block
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}

2
package-lock.json generated
View File

@@ -12,7 +12,7 @@
"node-fetch": "^3.3.2" "node-fetch": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"sass": "^1.90.0" "sass": "^1.69.5"
} }
}, },
"node_modules/@parcel/watcher": { "node_modules/@parcel/watcher": {

20
scripts/build.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/sh
# Exit immediately if a command exits with a non-zero status.
set -e
echo "Building CSS..."
# Assuming sass is installed and available in the environment or Docker image
# Compile theme.scss to theme.css
sass assets/css/scss/theme.scss static/assets/css/theme.css
echo "Fetching content from WordPress..."
# Assuming Node.js is installed and available in the environment or Docker image
node scripts/fetch-wordpress.js
echo "Generating production build with Hugo..."
# Assuming Hugo is installed and available in the environment or Docker image
hugo --minify --environment production --config hugo.toml
echo "Build process completed successfully!"

View File

@@ -68,12 +68,63 @@ async function generateData() {
modified: page.modified modified: page.modified
})); }));
// Create author-post mapping
const authorPosts = {};
posts.forEach(post => {
if (post.status === 'publish') {
const author = post._embedded?.author?.[0];
if (author) {
const authorSlug = author.slug;
if (!authorPosts[authorSlug]) {
authorPosts[authorSlug] = {
id: author.id,
name: author.name,
slug: author.slug,
description: author.description || '',
avatar: author.avatar_urls || {},
link: author.link,
posts: []
};
}
authorPosts[authorSlug].posts.push({
id: post.id,
title: post.title.rendered,
slug: post.slug,
date: post.date,
excerpt: post.excerpt.rendered,
featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url || '',
categories: (post._embedded?.['wp:term']?.[0] || []).map(cat => ({
id: cat.id,
name: cat.name,
slug: cat.slug
})),
tags: (post._embedded?.['wp:term']?.[1] || []).map(tag => ({
id: tag.id,
name: tag.name,
slug: tag.slug
}))
});
}
}
});
// Save data as JSON files // Save data as JSON files
fs.writeFileSync(path.join(OUTPUT_DIR, 'posts.json'), JSON.stringify(posts, null, 2)); 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, 'pages.json'), JSON.stringify(publishedPages, null, 2));
// Extract category descriptions
const categoriesWithDescriptions = categories.map(cat => ({
id: cat.id,
name: cat.name,
slug: cat.slug,
description: cat.description || '',
count: cat.count || 0
}));
// Also save to wordpress directory
fs.writeFileSync(path.join(OUTPUT_DIR, 'categories.json'), JSON.stringify(categories, 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, '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, 'authors.json'), JSON.stringify(authors, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'author-posts.json'), JSON.stringify(authorPosts, null, 2));
fs.writeFileSync(path.join(OUTPUT_DIR, 'navigation.json'), JSON.stringify(navigationData, null, 2)); fs.writeFileSync(path.join(OUTPUT_DIR, 'navigation.json'), JSON.stringify(navigationData, null, 2));
console.log(`✅ Fetched ${posts.length} posts, ${publishedPages.length} pages, ${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`);

View File

@@ -161,22 +161,47 @@ function generateAuthorDirectories(posts) {
fs.mkdirSync(AUTHORS_DIR, { recursive: true }); fs.mkdirSync(AUTHORS_DIR, { recursive: true });
} }
// Group posts by author // Group posts by author using proper author data
const postsByAuthor = {}; const postsByAuthor = {};
posts.forEach(post => { posts.forEach(post => {
const authorName = post._embedded?.author?.[0]?.name || 'Unknown'; const author = post._embedded?.author?.[0];
const authorSlug = authorName.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''); if (author) {
const authorSlug = author.slug;
const authorName = author.name;
if (!postsByAuthor[authorSlug]) { if (!postsByAuthor[authorSlug]) {
postsByAuthor[authorSlug] = { postsByAuthor[authorSlug] = {
id: author.id,
name: authorName, name: authorName,
slug: authorSlug, slug: authorSlug,
description: author.description || '',
avatar: author.avatar_urls || {},
link: author.link,
posts: [] posts: []
}; };
} }
postsByAuthor[authorSlug].posts.push(post); postsByAuthor[authorSlug].posts.push(post);
} else {
// Handle unknown author
const unknownSlug = 'unknown';
const unknownName = 'Unknown';
if (!postsByAuthor[unknownSlug]) {
postsByAuthor[unknownSlug] = {
id: 0,
name: unknownName,
slug: unknownSlug,
description: '',
avatar: {},
link: '',
posts: []
};
}
postsByAuthor[unknownSlug].posts.push(post);
}
}); });
// Create author directories and index pages // Create author directories and index pages
@@ -187,13 +212,17 @@ function generateAuthorDirectories(posts) {
fs.mkdirSync(authorDir, { recursive: true }); fs.mkdirSync(authorDir, { recursive: true });
} }
// Generate author index page // Generate author index page with proper metadata
const frontmatter = { const frontmatter = {
title: `Lise des articles de ${author.name}`, title: `Articles de ${author.name}`,
type: 'authors', type: 'authors',
layout: 'list', layout: 'list',
author: author.name, author: author.name,
author_slug: author.slug author_slug: author.slug,
description: author.description,
avatar: author.avatar,
link: author.link,
post_count: author.posts.length
}; };
const content = `--- const content = `---

View File

@@ -8494,6 +8494,49 @@ body.loaded:after {
} }
} }
.author-card-item {
margin-bottom: 2rem;
border: 1px solid #eee;
padding: 1rem;
display: flex;
align-items: center;
}
.author-card-item .author-card-image {
flex: 0 0 33.333333%;
max-width: 33.333333%;
padding-right: 1rem;
}
.author-card-item .author-card-image img {
border-radius: 50%;
width: 100%;
height: auto;
display: block;
}
.author-card-item .author-card-info {
flex: 0 0 66.666667%;
max-width: 66.666667%;
padding-left: 1rem;
}
.author-card-item .author-card-info .author-card-name {
font-size: 1.25rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.author-card-item .author-card-info .author-card-title {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
.author-card-item .author-card-info .author-card-buttons .author-button-link {
display: inline-flex;
align-items: center;
color: #007bff;
text-decoration: none;
}
.author-card-item .author-card-info .author-card-buttons .author-button-link svg {
margin-left: 0.5rem;
}
:root { :root {
--bs-dark-rgb: 24, 28, 32; --bs-dark-rgb: 24, 28, 32;
--bs-border-color: get-color("dark", 0.1); --bs-border-color: get-color("dark", 0.1);

File diff suppressed because one or more lines are too long

View File

@@ -2,46 +2,6 @@
# yarn lockfile v1 # 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": "@parcel/watcher-linux-x64-glibc@2.5.1":
version "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" resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz"
@@ -52,21 +12,6 @@
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz" resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz"
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== 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": "@parcel/watcher@^2.4.1":
version "2.5.1" version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz" resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
@@ -201,7 +146,7 @@ readdirp@^4.0.1:
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz" resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
sass@^1.90.0: sass@^1.69.5:
version "1.90.0" version "1.90.0"
resolved "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz" resolved "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz"
integrity sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q== integrity sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==