SHA256
Vorbereitung mit install.sh
This commit is contained in:
@@ -22,7 +22,7 @@ dotnet build -c Release
|
|||||||
# Beispiel-Seite erzeugen und bauen
|
# Beispiel-Seite erzeugen und bauen
|
||||||
dotnet bin/Release/net10.0/webstatic.dll init mysite
|
dotnet bin/Release/net10.0/webstatic.dll init mysite
|
||||||
cd mysite
|
cd mysite
|
||||||
dotnet ../../bin/Release/net10.0/webstatic.dll build --env work
|
dotnet ../../bin/Release/net10.0/webstatic.dll build --env development
|
||||||
|
|
||||||
# Lokal ansehen
|
# Lokal ansehen
|
||||||
dotnet ../../bin/Release/net10.0/webstatic.dll serve --port 8000
|
dotnet ../../bin/Release/net10.0/webstatic.dll serve --port 8000
|
||||||
@@ -30,14 +30,13 @@ dotnet ../../bin/Release/net10.0/webstatic.dll serve --port 8000
|
|||||||
|
|
||||||
## Umgebungen
|
## Umgebungen
|
||||||
|
|
||||||
| `--env` | baseUrl (Beispiel) | Zweck |
|
| `--env` | baseUrl (Beispiel) | Zweck |
|
||||||
|----------|------------------------------|------------------------|
|
|--------------|------------------------------|------------------------|
|
||||||
| `work` | `http://localhost:8000` | Lokale Entwicklung |
|
| `development`| `http://localhost:8000` | Lokale Entwicklung (work/preview/review) |
|
||||||
| `review` | `https://review.gitcover.org`| Freigabe/Vorschau |
|
| `staging` | `https://gcbok.gitcover.org` | Abnahme (brx5) |
|
||||||
| `staging`| `https://gcbok.gitcover.org` | Abnahme (brx5) |
|
| `deploy` | `https://gcbok.org` | Production |
|
||||||
| `deploy` | `https://gcbok.org` | Production |
|
|
||||||
|
|
||||||
Aliase: `--preview` = `work`, `--staging` = `staging`, `--deploy` = `deploy`.
|
Aliase: `--preview` / `--work` / `--review` = `development`, `--staging` = `staging`, `--deploy` = `deploy`.
|
||||||
|
|
||||||
## Konfiguration (`webstatic.json`)
|
## Konfiguration (`webstatic.json`)
|
||||||
|
|
||||||
|
|||||||
+11
-7
@@ -13,9 +13,12 @@ JavaScript-Framework.
|
|||||||
```bash
|
```bash
|
||||||
git clone <repo> build_webstatic
|
git clone <repo> build_webstatic
|
||||||
cd build_webstatic
|
cd build_webstatic
|
||||||
./install.sh # installiert nach /opt/GitCover/webstatic
|
./install.sh # klont nach /opt/GitCover/webstatic (Default: Clone-Modus)
|
||||||
```
|
```
|
||||||
Nach der Installation ist `webstatic` im PATH verfügbar.
|
Der Clone-Modus legt ein echtes Git-Repo in `/opt/GitCover/webstatic` an, sodass
|
||||||
|
spätere Updates via `git pull` (mit `git stash`/`git stash pop` für lokale
|
||||||
|
Anpassungen) möglich sind. Alternativ `WEBSTATIC_INSTALL_MODE=copy ./install.sh`
|
||||||
|
für eine reine Binary-Kopie. Nach der Installation ist `webstatic` im PATH verfügbar.
|
||||||
|
|
||||||
## 3. Neue Website anlegen
|
## 3. Neue Website anlegen
|
||||||
```bash
|
```bash
|
||||||
@@ -49,19 +52,20 @@ Ihr Inhalt …
|
|||||||
```
|
```
|
||||||
|
|
||||||
- `order` steuert die Reihenfolge in Navigation und Sidebar.
|
- `order` steuert die Reihenfolge in Navigation und Sidebar.
|
||||||
- `order: 0` = Banner, der auf der Startseite (order 1) eingebettet wird.
|
- `order: 0` = Banner (eigene Seite, getrennt vom Hero/`order: 1`).
|
||||||
- Sprachvarianten: `02-start.en.md`, `02-start.vi.md` (Fallback auf Basisdatei).
|
- Sprachvarianten: `02-start.de.md` (Default), `02-start.en.md`, `02-start.vi.md`
|
||||||
|
(fehlende Sprache fällt auf `.de.md` zurück).
|
||||||
|
|
||||||
## 5. Bauen & Vorschau
|
## 5. Bauen & Vorschau
|
||||||
```bash
|
```bash
|
||||||
webstatic build --env work # localhost:8000
|
webstatic build --env development # localhost:8000
|
||||||
webstatic serve --port 8000 # lokaler Server (Strg+C beendet)
|
webstatic serve --port 8000 # lokaler Server (Strg+C beendet)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Veröffentlichen
|
## 6. Veröffentlichen
|
||||||
| Ziel | Befehl |
|
| Ziel | Befehl |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| Review | `webstatic build --env review` |
|
| Development (lokal, work/preview/review) | `webstatic build --env development` |
|
||||||
| Staging | `webstatic build --env staging` |
|
| Staging | `webstatic build --env staging` |
|
||||||
| Production | `webstatic build --env deploy` |
|
| Production | `webstatic build --env deploy` |
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+59
-21
@@ -1,14 +1,25 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# GCBoK.WebStaticBuilder — KMU-Installationsskript
|
# GCBoK.WebStaticBuilder — Installationsskript
|
||||||
# Installiert das Tool nach /opt/GitCover/webstatic und legt einen Symlink
|
# Installiert das Tool nach /opt/GitCover/webstatic und legt einen Symlink
|
||||||
# `webstatic` nach /usr/local/bin. Idempotent.
|
# `webstatic` nach /usr/local/bin. Idempotent.
|
||||||
|
#
|
||||||
|
# Modi (WEBSTATIC_INSTALL_MODE):
|
||||||
|
# clone (Default) — klont das Quell-Repo inkl. .git nach /opt/GitCover/webstatic.
|
||||||
|
# Ermoeglicht spaeter `git pull` + lokale Anpassungen stashen/mergen
|
||||||
|
# (Hermes-Update-Konzept). Das Ziel ist ein echtes Git-Repo.
|
||||||
|
# copy — kopiert nur das gebaute Binary + Beispiel + Docs (kein .git).
|
||||||
|
#
|
||||||
|
# Hinweis: /opt ist root-owned. Das Script sinnvollerweise als root ausfuehren,
|
||||||
|
# z.B. `sudo env PATH="$PATH" ./install.sh`, damit `dotnet` im PATH bleibt.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
INSTALL_MODE="${WEBSTATIC_INSTALL_MODE:-clone}"
|
||||||
INSTALL_DIR="${WEBSTATIC_INSTALL_DIR:-/opt/GitCover/webstatic}"
|
INSTALL_DIR="${WEBSTATIC_INSTALL_DIR:-/opt/GitCover/webstatic}"
|
||||||
BIN_LINK="/usr/local/bin/webstatic"
|
BIN_LINK="/usr/local/bin/webstatic"
|
||||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
echo "== GCBoK.WebStaticBuilder Installer =="
|
echo "== GCBoK.WebStaticBuilder Installer =="
|
||||||
|
echo "Modus: $INSTALL_MODE"
|
||||||
echo "Installationsziel: $INSTALL_DIR"
|
echo "Installationsziel: $INSTALL_DIR"
|
||||||
|
|
||||||
# 1) .NET 10 pruefen
|
# 1) .NET 10 pruefen
|
||||||
@@ -17,34 +28,61 @@ if ! command -v dotnet >/dev/null 2>&1; then
|
|||||||
echo "Installiere .NET 10 von https://dotnet.microsoft.com/download" >&2
|
echo "Installiere .NET 10 von https://dotnet.microsoft.com/download" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
DOTNET_VER="$(dotnet --version 2>/dev/null || echo 0)"
|
echo "Gefunden: dotnet $(dotnet --version 2>/dev/null || echo unbekannt)"
|
||||||
echo "Gefunden: dotnet $DOTNET_VER"
|
|
||||||
|
|
||||||
# 2) Projekt bauen (Release)
|
# 2) Quelle beschaffen
|
||||||
|
if [ "$INSTALL_MODE" = "clone" ]; then
|
||||||
|
echo "== Clone-Modus: bereite $INSTALL_DIR vor =="
|
||||||
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||||
|
echo "Bereits ein Git-Repo — ueberspringe Clone (git pull ist beim Update noetig)."
|
||||||
|
else
|
||||||
|
if [ -e "$INSTALL_DIR" ]; then
|
||||||
|
echo "FEHLER: $INSTALL_DIR existiert, ist aber kein Git-Repo." >&2
|
||||||
|
echo "Entferne oder verschiebe es manuell, dann erneut ausfuehren." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Lokaler Klon (offline-sicher); Remote wird auf das echte Upstream gesetzt.
|
||||||
|
git clone "$REPO_DIR" "$INSTALL_DIR"
|
||||||
|
SRC_REMOTE="$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)"
|
||||||
|
if [ -n "$SRC_REMOTE" ]; then
|
||||||
|
git -C "$INSTALL_DIR" remote set-url origin "$SRC_REMOTE" 2>/dev/null || true
|
||||||
|
echo "Remote 'origin' -> $SRC_REMOTE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
BUILD_PROJECT="$INSTALL_DIR/GCBoK.WebStaticBuilder.csproj"
|
||||||
|
DLL="$INSTALL_DIR/bin/Release/net10.0/webstatic.dll"
|
||||||
|
else
|
||||||
|
echo "== Copy-Modus: baue Release aus $REPO_DIR =="
|
||||||
|
dotnet build "$REPO_DIR/GCBoK.WebStaticBuilder.csproj" -c Release -v minimal
|
||||||
|
echo "== Kopiere nach $INSTALL_DIR =="
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
rm -rf "$INSTALL_DIR/bin" "$INSTALL_DIR/webstatic.example" 2>/dev/null || true
|
||||||
|
cp -r "$REPO_DIR/bin/Release/net10.0/." "$INSTALL_DIR/"
|
||||||
|
cp -r "$REPO_DIR/webstatic.example" "$INSTALL_DIR/webstatic.example" 2>/dev/null || true
|
||||||
|
for f in LICENSE README.md docs plans AGENTS.md MEMORY.md SKILLS.md; do
|
||||||
|
[ -e "$REPO_DIR/$f" ] && cp -r "$REPO_DIR/$f" "$INSTALL_DIR/" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
BUILD_PROJECT="$REPO_DIR/GCBoK.WebStaticBuilder.csproj"
|
||||||
|
DLL="$INSTALL_DIR/webstatic.dll"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) Bauen (im Installationsziel bei Clone, sonst im Repo)
|
||||||
echo "== Baue Release =="
|
echo "== Baue Release =="
|
||||||
dotnet build "$REPO_DIR/GCBoK.WebStaticBuilder.csproj" -c Release -v minimal
|
dotnet build "$BUILD_PROJECT" -c Release -v minimal
|
||||||
|
|
||||||
# 3) Nach /opt kopieren
|
|
||||||
echo "== Kopiere nach $INSTALL_DIR =="
|
|
||||||
mkdir -p "$INSTALL_DIR"
|
|
||||||
rm -rf "$INSTALL_DIR/bin" "$INSTALL_DIR/webstatic.example" 2>/dev/null || true
|
|
||||||
cp -r "$REPO_DIR/bin/Release/net10.0/." "$INSTALL_DIR/"
|
|
||||||
cp -r "$REPO_DIR/webstatic.example" "$INSTALL_DIR/webstatic.example" 2>/dev/null || true
|
|
||||||
# Hilfsdateien auf oberster Ebene mitnehmen (LICENSE, README, docs)
|
|
||||||
for f in LICENSE README.md docs plans AGENTS.md MEMORY.md SKILLS.md; do
|
|
||||||
[ -e "$REPO_DIR/$f" ] && cp -r "$REPO_DIR/$f" "$INSTALL_DIR/" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
|
|
||||||
# 4) Symlink
|
# 4) Symlink
|
||||||
echo "== Verknuepfe $BIN_LINK -> $INSTALL_DIR/webstatic.dll =="
|
echo "== Verknuepfe $BIN_LINK -> $DLL =="
|
||||||
if [ -w "$(dirname "$BIN_LINK")" ]; then
|
if [ -w "$(dirname "$BIN_LINK")" ]; then
|
||||||
ln -sf "$INSTALL_DIR/webstatic.dll" "$BIN_LINK"
|
ln -sf "$DLL" "$BIN_LINK"
|
||||||
else
|
else
|
||||||
echo "Hinweis: $BIN_LINK benoetigt Schreibrechte; erstelle Link via sudo."
|
echo "Hinweis: $BIN_LINK benoetigt Schreibrechte; erstelle Link via sudo."
|
||||||
sudo ln -sf "$INSTALL_DIR/webstatic.dll" "$BIN_LINK"
|
sudo ln -sf "$DLL" "$BIN_LINK"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Fertig. Danach verfuegbar ueber: webstatic --help"
|
echo "Fertig. Danach verfuegbar ueber: webstatic --help"
|
||||||
echo "Beispiel-Seite erzeugen: webstatic init mysite && cd mysite && webstatic build --env work"
|
echo "Beispiel-Seite erzeugen: webstatic init mysite && cd mysite && webstatic build --env development"
|
||||||
echo "Umgebungsvariable (optional): WEBSTATIC_BUILDER=$INSTALL_DIR/webstatic.dll"
|
echo "GCBoK bauen: webstatic build --root www --env development"
|
||||||
|
echo "Umgebungsvariable (optional): WEBSTATIC_BUILDER=$DLL"
|
||||||
|
echo ""
|
||||||
|
echo "Update (Clone-Modus): cd $INSTALL_DIR && git stash && git pull && git stash pop && dotnet build -c Release"
|
||||||
|
|||||||
+45
-7
@@ -36,7 +36,20 @@ public static class Builder
|
|||||||
Directory.CreateDirectory(Directories.DistDir);
|
Directory.CreateDirectory(Directories.DistDir);
|
||||||
CopyAssets(verbose);
|
CopyAssets(verbose);
|
||||||
|
|
||||||
var langLinksHtml = BuildLangLinksHtml();
|
// Precompute, for every page, its slug per language (keyed by order|section),
|
||||||
|
// so the language switcher can link to the equivalent page in each language
|
||||||
|
// even when slugs differ between translations.
|
||||||
|
var pageSlugs = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
foreach (var l in cfg.LanguageCodes())
|
||||||
|
{
|
||||||
|
foreach (var p in ContentLoader.LoadContent(Directories.ContentDir, l))
|
||||||
|
{
|
||||||
|
var key = $"{p.Meta.Order ?? -1}|{p.Meta.Section ?? ""}";
|
||||||
|
if (!pageSlugs.TryGetValue(key, out var m))
|
||||||
|
pageSlugs[key] = m = new Dictionary<string, string>();
|
||||||
|
m[l] = p.Href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var lang in cfg.LanguageCodes())
|
foreach (var lang in cfg.LanguageCodes())
|
||||||
{
|
{
|
||||||
@@ -90,6 +103,17 @@ public static class Builder
|
|||||||
var sidebarTocHtml = BuildSidebarTocHtml(page.Content);
|
var sidebarTocHtml = BuildSidebarTocHtml(page.Content);
|
||||||
var pageNavHtml = (page.Meta.Order == 1) ? "" : BuildPageNavHtml(contents, page, lang);
|
var pageNavHtml = (page.Meta.Order == 1) ? "" : BuildPageNavHtml(contents, page, lang);
|
||||||
|
|
||||||
|
var introHtml = string.IsNullOrEmpty(page.Meta.Intro)
|
||||||
|
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Intro);
|
||||||
|
var positioningHtml = string.IsNullOrEmpty(page.Meta.Positioning)
|
||||||
|
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Positioning);
|
||||||
|
var publisherHtml = string.IsNullOrEmpty(page.Meta.Publisher)
|
||||||
|
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Publisher);
|
||||||
|
|
||||||
|
var langKey = $"{page.Meta.Order ?? -1}|{page.Meta.Section ?? ""}";
|
||||||
|
var langLinksHtml = BuildLangLinksHtml(
|
||||||
|
pageSlugs.TryGetValue(langKey, out var m) ? m : null);
|
||||||
|
|
||||||
var pageHtml = layout
|
var pageHtml = layout
|
||||||
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
||||||
.Replace("{{ json_ld }}", "").Replace("{{json_ld}}", "")
|
.Replace("{{ json_ld }}", "").Replace("{{json_ld}}", "")
|
||||||
@@ -102,6 +126,9 @@ public static class Builder
|
|||||||
.Replace("{{lang_links}}", langLinksHtml).Replace("{{ lang_links }}", langLinksHtml)
|
.Replace("{{lang_links}}", langLinksHtml).Replace("{{ lang_links }}", langLinksHtml)
|
||||||
.Replace("{{glossary_href}}", GlossaryHref(lang)).Replace("{{ glossary_href }}", GlossaryHref(lang))
|
.Replace("{{glossary_href}}", GlossaryHref(lang)).Replace("{{ glossary_href }}", GlossaryHref(lang))
|
||||||
.Replace("{{glossary_label}}", GlossaryLabel(lang)).Replace("{{ glossary_label }}", GlossaryLabel(lang))
|
.Replace("{{glossary_label}}", GlossaryLabel(lang)).Replace("{{ glossary_label }}", GlossaryLabel(lang))
|
||||||
|
.Replace("{{intro}}", introHtml).Replace("{{ intro }}", introHtml)
|
||||||
|
.Replace("{{positioning}}", positioningHtml).Replace("{{ positioning }}", positioningHtml)
|
||||||
|
.Replace("{{publisher}}", publisherHtml).Replace("{{ publisher }}", publisherHtml)
|
||||||
.Replace("{{content}}", fullBody).Replace("{{ content }}", fullBody)
|
.Replace("{{content}}", fullBody).Replace("{{ content }}", fullBody)
|
||||||
.Replace("{{body}}", fullBody).Replace("{{ body }}", fullBody);
|
.Replace("{{body}}", fullBody).Replace("{{ body }}", fullBody);
|
||||||
|
|
||||||
@@ -188,7 +215,13 @@ public static class Builder
|
|||||||
File.AppendAllText(Directories.BuildLogPath, $" Assets copied to: {dstDir}\n");
|
File.AppendAllText(Directories.BuildLogPath, $" Assets copied to: {dstDir}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildLangLinksHtml()
|
/// <summary>
|
||||||
|
/// Build the language-switch dropdown for the current page. <paramref name="langSlug"/>
|
||||||
|
/// maps each language code to the current page's slug in that language (resolved by
|
||||||
|
/// shared identity — order + section — so each language links to its own equivalent
|
||||||
|
/// page). When null, all links point to the language root.
|
||||||
|
/// </summary>
|
||||||
|
private static string BuildLangLinksHtml(Dictionary<string, string>? langSlug)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
foreach (var kv in Directories.Config.Languages)
|
foreach (var kv in Directories.Config.Languages)
|
||||||
@@ -197,7 +230,10 @@ public static class Builder
|
|||||||
var li = kv.Value;
|
var li = kv.Value;
|
||||||
var label = li.Label ?? li.Name;
|
var label = li.Label ?? li.Name;
|
||||||
var code = (li.HrefLang ?? lang).ToUpperInvariant();
|
var code = (li.HrefLang ?? lang).ToUpperInvariant();
|
||||||
sb.AppendLine($" <li><a href=\"/{lang}/\" hreflang=\"{li.HrefLang ?? lang}\"><span>{HtmlEncode(label)}</span> <span class=\"lang-dropdown__code\">{code}</span></a></li>");
|
string slug = "";
|
||||||
|
if (langSlug != null && langSlug.TryGetValue(lang, out var h)) slug = h.Trim('/');
|
||||||
|
var href = string.IsNullOrEmpty(slug) ? $"/{lang}/" : $"/{lang}/{slug}/";
|
||||||
|
sb.AppendLine($" <li><a href=\"{href}\" hreflang=\"{li.HrefLang ?? lang}\"><span>{HtmlEncode(label)}</span> <span class=\"lang-dropdown__code\">{code}</span></a></li>");
|
||||||
}
|
}
|
||||||
return sb.ToString().Trim();
|
return sb.ToString().Trim();
|
||||||
}
|
}
|
||||||
@@ -227,7 +263,7 @@ public static class Builder
|
|||||||
.OrderBy(p => p.Meta.Order ?? 999).ToList();
|
.OrderBy(p => p.Meta.Order ?? 999).ToList();
|
||||||
var heroPage = contents.FirstOrDefault(p => p.Meta.Order == 1);
|
var heroPage = contents.FirstOrDefault(p => p.Meta.Order == 1);
|
||||||
if (heroPage != null)
|
if (heroPage != null)
|
||||||
sb.AppendLine(" <li><a href=\"index.html\"><span class=\"nav-idx\">★</span> Startseite</a></li>");
|
sb.AppendLine($" <li><a href=\"index.html\"><span class=\"nav-idx\">★</span> {I18n.Get(lang, "nav.home")}</a></li>");
|
||||||
|
|
||||||
foreach (var page in navPages)
|
foreach (var page in navPages)
|
||||||
{
|
{
|
||||||
@@ -285,7 +321,7 @@ public static class Builder
|
|||||||
var prevLabel = (prev.Meta.Eyebrow ?? prev.Meta.LocalizedTitle(lang, prev.Meta.Title ?? ""));
|
var prevLabel = (prev.Meta.Eyebrow ?? prev.Meta.LocalizedTitle(lang, prev.Meta.Title ?? ""));
|
||||||
if (prevLabel.Contains(" — ")) prevLabel = prevLabel.Substring(prevLabel.IndexOf(" — ") + 3);
|
if (prevLabel.Contains(" — ")) prevLabel = prevLabel.Substring(prevLabel.IndexOf(" — ") + 3);
|
||||||
sb.AppendLine($" <a class=\"page-nav__link\" href=\"{prev.Href.Trim('/')}.html\">");
|
sb.AppendLine($" <a class=\"page-nav__link\" href=\"{prev.Href.Trim('/')}.html\">");
|
||||||
sb.AppendLine($" <span class=\"page-nav__label\">← Vorherige</span>");
|
sb.AppendLine($" <span class=\"page-nav__label\">{I18n.Get(lang, "nav.prev")}</span>");
|
||||||
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(prevLabel)}</span>");
|
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(prevLabel)}</span>");
|
||||||
sb.AppendLine(" </a>");
|
sb.AppendLine(" </a>");
|
||||||
}
|
}
|
||||||
@@ -297,7 +333,7 @@ public static class Builder
|
|||||||
var nextLabel = (next.Meta.Eyebrow ?? next.Meta.LocalizedTitle(lang, next.Meta.Title ?? ""));
|
var nextLabel = (next.Meta.Eyebrow ?? next.Meta.LocalizedTitle(lang, next.Meta.Title ?? ""));
|
||||||
if (nextLabel.Contains(" — ")) nextLabel = nextLabel.Substring(nextLabel.IndexOf(" — ") + 3);
|
if (nextLabel.Contains(" — ")) nextLabel = nextLabel.Substring(nextLabel.IndexOf(" — ") + 3);
|
||||||
sb.AppendLine($" <a class=\"page-nav__link page-nav__link--next\" href=\"{next.Href.Trim('/')}.html\">");
|
sb.AppendLine($" <a class=\"page-nav__link page-nav__link--next\" href=\"{next.Href.Trim('/')}.html\">");
|
||||||
sb.AppendLine($" <span class=\"page-nav__label\">Nächste →</span>");
|
sb.AppendLine($" <span class=\"page-nav__label\">{I18n.Get(lang, "nav.next")}</span>");
|
||||||
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(nextLabel)}</span>");
|
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(nextLabel)}</span>");
|
||||||
sb.AppendLine(" </a>");
|
sb.AppendLine(" </a>");
|
||||||
}
|
}
|
||||||
@@ -324,7 +360,6 @@ public static class Builder
|
|||||||
var legalDir = Directories.LegalDir;
|
var legalDir = Directories.LegalDir;
|
||||||
if (!Directory.Exists(legalDir)) return;
|
if (!Directory.Exists(legalDir)) return;
|
||||||
var layout = LoadLayout();
|
var layout = LoadLayout();
|
||||||
var langLinksHtml = BuildLangLinksHtml();
|
|
||||||
|
|
||||||
foreach (var lang in cfg.LanguageCodes())
|
foreach (var lang in cfg.LanguageCodes())
|
||||||
{
|
{
|
||||||
@@ -358,6 +393,9 @@ public static class Builder
|
|||||||
var seoHead = SeoHelper.RenderSeoHead(lang, title, "", $"{env.BaseUrl}{canonical}",
|
var seoHead = SeoHelper.RenderSeoHead(lang, title, "", $"{env.BaseUrl}{canonical}",
|
||||||
env.Cdn, defaultTitle, defaultDesc);
|
env.Cdn, defaultTitle, defaultDesc);
|
||||||
var sidebarTocHtml = BuildSidebarTocHtml(content);
|
var sidebarTocHtml = BuildSidebarTocHtml(content);
|
||||||
|
var legalSlug = $"/{Path.GetFileNameWithoutExtension(baseName)}/";
|
||||||
|
var legalLangSlug = cfg.LanguageCodes().ToDictionary(l => l, l => legalSlug);
|
||||||
|
var langLinksHtml = BuildLangLinksHtml(legalLangSlug);
|
||||||
|
|
||||||
var pageHtml = layout
|
var pageHtml = layout
|
||||||
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
||||||
|
|||||||
+35
-26
@@ -7,49 +7,58 @@ namespace GCBoK.WebStaticBuilder;
|
|||||||
/// <summary>Content loading and YAML frontmatter parsing.</summary>
|
/// <summary>Content loading and YAML frontmatter parsing.</summary>
|
||||||
public static class ContentLoader
|
public static class ContentLoader
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Load all content files for a language. Files are grouped by their base name
|
||||||
|
/// (language suffix stripped). The requested language is preferred; if it is
|
||||||
|
/// missing, the German (<c>.de.md</c>) variant is used as fallback, and finally
|
||||||
|
/// an un-suffixed default. This makes German the canonical default content
|
||||||
|
/// source, derived explicitly from <c>.de.md</c> rather than an ambiguous
|
||||||
|
/// suffixless file.
|
||||||
|
/// </summary>
|
||||||
public static List<PageInfo> LoadContent(string contentDir, string lang)
|
public static List<PageInfo> LoadContent(string contentDir, string lang)
|
||||||
{
|
{
|
||||||
var results = new List<PageInfo>();
|
var byBase = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
|
||||||
foreach (var filepath in Directory.GetFiles(contentDir, "*.md", SearchOption.TopDirectoryOnly))
|
foreach (var filepath in Directory.GetFiles(contentDir, "*.md", SearchOption.TopDirectoryOnly))
|
||||||
{
|
{
|
||||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(filepath);
|
var nameWithoutExt = Path.GetFileNameWithoutExtension(filepath);
|
||||||
|
|
||||||
string? fileLang = null;
|
string? fileLang = null;
|
||||||
if (nameWithoutExt.Contains("."))
|
if (nameWithoutExt.Contains('.'))
|
||||||
{
|
{
|
||||||
var parts = nameWithoutExt.Split('.');
|
var lastPart = nameWithoutExt.Split('.')[^1].ToLowerInvariant();
|
||||||
if (parts.Length >= 2)
|
if (lastPart is "de" or "en" or "vi")
|
||||||
{
|
fileLang = lastPart;
|
||||||
var lastPart = parts[^1].ToLowerInvariant();
|
else
|
||||||
if (lastPart == "de" || lastPart == "en" || lastPart == "vi")
|
continue;
|
||||||
{
|
|
||||||
fileLang = lastPart;
|
|
||||||
if (fileLang != lang) continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = File.ReadAllText(filepath);
|
var baseName = fileLang == null
|
||||||
|
? nameWithoutExt
|
||||||
|
: nameWithoutExt.Substring(0, nameWithoutExt.Length - fileLang.Length - 1);
|
||||||
|
|
||||||
|
if (!byBase.TryGetValue(baseName, out var map))
|
||||||
|
byBase[baseName] = map = new Dictionary<string, string>();
|
||||||
|
map[fileLang ?? ""] = filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new List<PageInfo>();
|
||||||
|
foreach (var map in byBase.Values)
|
||||||
|
{
|
||||||
|
string? chosen = null;
|
||||||
|
if (map.TryGetValue(lang, out var langPath)) chosen = langPath;
|
||||||
|
else if (map.TryGetValue("de", out var dePath)) chosen = dePath;
|
||||||
|
else if (map.TryGetValue("", out var defPath)) chosen = defPath;
|
||||||
|
if (chosen == null) continue;
|
||||||
|
|
||||||
|
var text = File.ReadAllText(chosen);
|
||||||
var (meta, content) = ParseFrontmatter(text);
|
var (meta, content) = ParseFrontmatter(text);
|
||||||
|
|
||||||
var langs = meta.Langs ?? "";
|
var langs = meta.Langs ?? "";
|
||||||
if (!string.IsNullOrEmpty(langs) && !langs.Split(',').Select(l => l.Trim()).Contains(lang))
|
if (!string.IsNullOrEmpty(langs) && !langs.Split(',').Select(l => l.Trim()).Contains(lang))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (fileLang == null && lang != "de")
|
results.Add(new PageInfo(chosen, meta, GetHref(meta), content));
|
||||||
{
|
|
||||||
var baseName = nameWithoutExt;
|
|
||||||
var langSpecificPath = Path.Combine(contentDir, $"{baseName}.{lang}.md");
|
|
||||||
if (File.Exists(langSpecificPath))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.Add(new PageInfo(filepath, meta, GetHref(meta), content));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.OrderBy(x => x.Meta.Order ?? 999).ToList();
|
return results.OrderBy(x => x.Meta.Order ?? 999).ToList();
|
||||||
|
|||||||
+5
-4
@@ -73,10 +73,11 @@ if (argsList.Contains("init"))
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build
|
// Build
|
||||||
var envName = "work";
|
// "development" folds work, preview and review into a single local/dev environment.
|
||||||
if (Has("--preview")) envName = "work";
|
var envName = "development";
|
||||||
if (Has("--work")) envName = "work";
|
if (Has("--preview")) envName = "development";
|
||||||
if (Has("--review")) envName = "review";
|
if (Has("--work")) envName = "development";
|
||||||
|
if (Has("--review")) envName = "development";
|
||||||
if (Has("--staging")) envName = "staging";
|
if (Has("--staging")) envName = "staging";
|
||||||
if (Has("--deploy")) envName = "deploy";
|
if (Has("--deploy")) envName = "deploy";
|
||||||
if (Val("--env") is { } ev) envName = ev;
|
if (Val("--env") is { } ev) envName = ev;
|
||||||
|
|||||||
@@ -30,8 +30,7 @@
|
|||||||
"pagefind": true,
|
"pagefind": true,
|
||||||
"translator": { "provider": "azure", "key_env": "WEBSTATIC_TRANSLATOR_KEY" },
|
"translator": { "provider": "azure", "key_env": "WEBSTATIC_TRANSLATOR_KEY" },
|
||||||
"environments": {
|
"environments": {
|
||||||
"work": { "base_url": "http://localhost:8000", "cdn": "/assets" },
|
"development": { "base_url": "http://localhost:8000", "cdn": "/assets" },
|
||||||
"review": { "base_url": "https://review.gitcover.org", "cdn": "https://gitcover.org/assets" },
|
|
||||||
"staging": { "base_url": "https://gcbok.gitcover.org", "cdn": "https://gitcover.org/assets" },
|
"staging": { "base_url": "https://gcbok.gitcover.org", "cdn": "https://gitcover.org/assets" },
|
||||||
"deploy": { "base_url": "https://gcbok.org", "cdn": "https://gitcover.org/assets" }
|
"deploy": { "base_url": "https://gcbok.org", "cdn": "https://gitcover.org/assets" }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user