feat: series-таксономия, переключатель темы вправо, соцсети в меню
- hugo.toml: [taxonomies] series = "series"; [params.socialIcons] (github, telegram, rss) - layouts/partials/series_nav.html: модуль «Серия: N из M» с навигацией prev/next (сортировка по series_order), поддержка кириллических названий серий - layouts/_default/single.html: override темы — подключение series_nav перед контентом - layouts/partials/extend_footer.html: JS переносит .logo-switches из .logo в конец .nav (переключатель темы — справа после меню); server-rendered блок соцсетей переносится к меню; иконки — через штатный svg.html темы (github/telegram/rss) - assets/css/extended/typewriter.css: стили .social-icons-inline, .series-nav; .logo-switches после переноса попадает под .nav > .logo-switches (order:2) Closes openspec change series-taxonomy-and-header
This commit is contained in:
@@ -59,3 +59,59 @@ article.first-entry {
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
padding: var(--gap);
|
padding: var(--gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 3) social icons inside the header nav (moved there by JS in
|
||||||
|
extend_footer.html). Sizing matches the logo/menu scale. */
|
||||||
|
.social-icons-inline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-inline-start: 14px;
|
||||||
|
}
|
||||||
|
.social-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--primary);
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.social-icon svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4) series module (layouts/partials/series_nav.html) */
|
||||||
|
.series-nav {
|
||||||
|
margin-top: var(--gap);
|
||||||
|
margin-bottom: calc(var(--gap) * 0.5);
|
||||||
|
padding: calc(var(--gap) * 0.5) var(--gap);
|
||||||
|
background: var(--entry);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.series-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.series-title a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.series-title a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.series-progress {
|
||||||
|
color: var(--secondary);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.series-links {
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.series-nav a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.series-nav a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@ draft = false
|
|||||||
[taxonomies]
|
[taxonomies]
|
||||||
category = "categories"
|
category = "categories"
|
||||||
tag = "tags"
|
tag = "tags"
|
||||||
|
series = "series"
|
||||||
|
|
||||||
[markup]
|
[markup]
|
||||||
[markup.highlight]
|
[markup.highlight]
|
||||||
@@ -116,6 +117,19 @@ draft = false
|
|||||||
# twitter = "your_twitter_handle"
|
# twitter = "your_twitter_handle"
|
||||||
# facebook_admin = "your_facebook_admin_id"
|
# facebook_admin = "your_facebook_admin_id"
|
||||||
|
|
||||||
|
[[params.socialIcons]]
|
||||||
|
name = "github"
|
||||||
|
url = "https://github.com/kpa39l"
|
||||||
|
title = "GitHub"
|
||||||
|
[[params.socialIcons]]
|
||||||
|
name = "telegram"
|
||||||
|
url = "https://t.me/kpa39l"
|
||||||
|
title = "Telegram"
|
||||||
|
[[params.socialIcons]]
|
||||||
|
name = "rss"
|
||||||
|
url = "/index.xml"
|
||||||
|
title = "RSS"
|
||||||
|
|
||||||
[languages]
|
[languages]
|
||||||
[languages.ru]
|
[languages.ru]
|
||||||
languageName = "Русский"
|
languageName = "Русский"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{{- define "main" }}
|
||||||
|
|
||||||
|
<article class="post-single">
|
||||||
|
<header class="post-header">
|
||||||
|
{{ partial "breadcrumbs.html" . }}
|
||||||
|
<h1 class="post-title entry-hint-parent">
|
||||||
|
{{ .Title }}
|
||||||
|
{{- if .Draft }}
|
||||||
|
<span class="entry-hint" title="Draft">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="35" viewBox="0 -960 960 960" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
{{- end }}
|
||||||
|
</h1>
|
||||||
|
{{- if .Description }}
|
||||||
|
<div class="post-description">
|
||||||
|
{{ .Description }}
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
{{- if not (.Param "hideMeta") }}
|
||||||
|
<div class="post-meta">
|
||||||
|
{{- partial "post_meta.html" . -}}
|
||||||
|
{{- partial "translation_list.html" . -}}
|
||||||
|
{{- partial "edit_post.html" . -}}
|
||||||
|
{{- partial "post_canonical.html" . -}}
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
</header>
|
||||||
|
{{- $isHidden := (.Param "cover.hiddenInSingle") | default (.Param "cover.hidden") | default false }}
|
||||||
|
{{- partial "cover.html" (dict "cxt" . "IsSingle" true "isHidden" $isHidden) }}
|
||||||
|
{{- if (.Param "ShowToc") }}
|
||||||
|
{{- partial "toc.html" . }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Content }}
|
||||||
|
{{- partial "series_nav.html" . -}}
|
||||||
|
<div class="post-content">
|
||||||
|
{{- if not (.Param "disableAnchoredHeadings") }}
|
||||||
|
{{- partial "anchored_headings.html" .Content -}}
|
||||||
|
{{- else }}{{ .Content }}{{ end }}
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
<footer class="post-footer">
|
||||||
|
{{- $tags := .Language.Params.Taxonomies.tag | default "tags" }}
|
||||||
|
<ul class="post-tags">
|
||||||
|
{{- range ($.GetTerms $tags) }}
|
||||||
|
<li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
|
||||||
|
{{- end }}
|
||||||
|
</ul>
|
||||||
|
{{- if (.Param "ShowPostNavLinks") }}
|
||||||
|
{{- partial "post_nav_links.html" . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and site.Params.ShowShareButtons (ne .Params.disableShare true)) }}
|
||||||
|
{{- partial "share_icons.html" . -}}
|
||||||
|
{{- end }}
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{{- if (.Param "comments") }}
|
||||||
|
{{- partial "comments.html" . }}
|
||||||
|
{{- end }}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{{- end }}{{/* end main */}}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
{{- /* dedinit() typewriter: animated topics inside logo parens.
|
{{- /* dedinit() typewriter: animated topics inside logo parens.
|
||||||
Pure JS + CSS, no Alpine. Progressive enhancement — no-JS keeps
|
Pure JS + CSS, no Alpine. Progressive enhancement — no-JS keeps
|
||||||
the plain "dedinit()" logo. Words configurable via hugo.toml:
|
the plain "dedinit()" logo. Words configurable via hugo.toml.
|
||||||
[params.typewriter] words = ["linux", "llm", "politics"] */ -}}
|
Also: moves theme toggle to the right end of .nav and injects
|
||||||
|
social icons (from [params.socialIcons]) next to the menu.
|
||||||
|
Icons are rendered server-side via the theme's svg.html partial. */ -}}
|
||||||
{{- $tw_words := site.Params.typewriter.words | default (slice "linux" "llm" "politics" "homelab" "nix") -}}
|
{{- $tw_words := site.Params.typewriter.words | default (slice "linux" "llm" "politics" "homelab" "nix") -}}
|
||||||
|
{{- $social := site.Params.socialIcons | default (slice) -}}
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
|
/* 1) typewriter */
|
||||||
const LOGO_SEL = 'header .logo a, .logo a';
|
const LOGO_SEL = 'header .logo a, .logo a';
|
||||||
const queue = [() => {
|
const queue = [() => {
|
||||||
const a = document.querySelector(LOGO_SEL);
|
const a = document.querySelector(LOGO_SEL);
|
||||||
@@ -18,21 +22,17 @@
|
|||||||
const box = document.createElement('span');
|
const box = document.createElement('span');
|
||||||
box.id = 'dedinit-typewriter';
|
box.id = 'dedinit-typewriter';
|
||||||
box.className = 'tw';
|
box.className = 'tw';
|
||||||
box.textContent = '';
|
|
||||||
box.appendChild(Object.assign(document.createElement('span'), { className: 'tw-cursor' }));
|
box.appendChild(Object.assign(document.createElement('span'), { className: 'tw-cursor' }));
|
||||||
// rebuild: <prefix>(<typewriter>)<suffix>
|
|
||||||
a.textContent = '';
|
a.textContent = '';
|
||||||
a.appendChild(document.createTextNode(text.slice(0, open + 1)));
|
a.appendChild(document.createTextNode(text.slice(0, open + 1)));
|
||||||
a.appendChild(box);
|
a.appendChild(box);
|
||||||
a.appendChild(document.createTextNode(text.slice(close)));
|
a.appendChild(document.createTextNode(text.slice(close)));
|
||||||
|
|
||||||
// reduced motion: blink is disabled via CSS; keep typing but no blink
|
|
||||||
const TYPE = 90, ERASE = 50, HOLD = 1400, GAP = 400;
|
const TYPE = 90, ERASE = 50, HOLD = 1400, GAP = 400;
|
||||||
let w = 0, i = 0, dir = 1;
|
let w = 0, i = 0, dir = 1;
|
||||||
box.querySelector('.tw-cursor').style.display = 'inline-block';
|
box.querySelector('.tw-cursor').style.display = 'inline-block';
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
const word = words[w];
|
const word = words[w];
|
||||||
// keep cursor as the last child; typed text goes before it
|
|
||||||
box.replaceChildren(document.createTextNode(word.slice(0, i)), box.querySelector('.tw-cursor'));
|
box.replaceChildren(document.createTextNode(word.slice(0, i)), box.querySelector('.tw-cursor'));
|
||||||
let delay;
|
let delay;
|
||||||
if (dir === 1) {
|
if (dir === 1) {
|
||||||
@@ -46,7 +46,45 @@
|
|||||||
};
|
};
|
||||||
tick();
|
tick();
|
||||||
}];
|
}];
|
||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => queue.shift()());
|
|
||||||
else queue.shift()();
|
/* 2) move theme toggle (logo-switches) to the end of .nav, after #menu.
|
||||||
|
It is nested inside .logo in the theme; appending to .nav moves it
|
||||||
|
visually to the far right (flex order handled by CSS). */
|
||||||
|
const toggleQueue = [() => {
|
||||||
|
const nav = document.querySelector('header .nav, .nav');
|
||||||
|
const sw = nav && nav.querySelector('.logo-switches');
|
||||||
|
if (!nav || !sw || sw.dataset.moved) return;
|
||||||
|
sw.dataset.moved = '1';
|
||||||
|
nav.appendChild(sw);
|
||||||
|
}];
|
||||||
|
|
||||||
|
/* 3) social icons: a hidden server-rendered container is moved into nav
|
||||||
|
next to the menu. The icons are rendered by Hugo (theme svg.html),
|
||||||
|
so no client-side icon data is needed. */
|
||||||
|
const socialQueue = [() => {
|
||||||
|
const nav = document.querySelector('header .nav, .nav');
|
||||||
|
const box = document.getElementById('__social-icons');
|
||||||
|
if (!nav || !box) return;
|
||||||
|
box.id = '';
|
||||||
|
const menu = nav.querySelector('#menu');
|
||||||
|
if (menu) menu.after(box);
|
||||||
|
else nav.appendChild(box);
|
||||||
|
}];
|
||||||
|
|
||||||
|
const run = () => { queue.shift()(); toggleQueue.shift()(); socialQueue.shift()(); };
|
||||||
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run);
|
||||||
|
else run();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
{{- /* server-rendered social icons; moved into the nav by the JS above.
|
||||||
|
display:none so no-JS visitors never see a stray block. */ -}}
|
||||||
|
{{- if $social -}}
|
||||||
|
<div id="__social-icons" class="social-icons-inline" style="display:none">
|
||||||
|
{{- range $social -}}
|
||||||
|
<a href="{{ trim .url " " | safeURL }}" target="_blank" rel="noopener noreferrer me"
|
||||||
|
title="{{ (.title | default .name) | title }}" class="social-icon">
|
||||||
|
{{- partial "svg.html" . -}}
|
||||||
|
</a>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{{- /* Серия: блок навигации по серии (как в Blowfish).
|
||||||
|
Использование: в single.html перед .post-content:
|
||||||
|
{{- partial "series_nav.html" . -}}
|
||||||
|
Пост помечается во front-matter:
|
||||||
|
series: ["Имя серии"]
|
||||||
|
series_order: 1
|
||||||
|
Выводит блок «Серия: N из M» со ссылками на предыдущую/следующую часть.
|
||||||
|
Если серий нет или пост не в серии — выводит пустоту.
|
||||||
|
*/ -}}
|
||||||
|
{{- $series := .Params.series -}}
|
||||||
|
{{- if $series -}}
|
||||||
|
{{- range $series -}}
|
||||||
|
{{- $seriesName := . -}}
|
||||||
|
{{- /* термины хранятся с URL-слизом: дооснастить и urlize, и lower */ -}}
|
||||||
|
{{- $slug := ($seriesName | urlize) -}}
|
||||||
|
{{- $term := (site.GetPage (printf "/series/%s/" $slug)) | default (site.GetPage (printf "/series/%s/" ($seriesName | lower | urlize))) -}}
|
||||||
|
{{- if not $term }}{{- $term = site.GetPage (printf "/series/%s" ($seriesName | lower)) -}}{{- end -}}
|
||||||
|
{{- if $term -}}
|
||||||
|
{{- $pages := (sort $term.Pages "Params.series_order" "asc") -}}
|
||||||
|
{{- $current := $.Params.series_order -}}
|
||||||
|
{{- $total := $pages | len -}}
|
||||||
|
{{- $idx := 0 -}}
|
||||||
|
{{- $prev := "" -}}
|
||||||
|
{{- $next := "" -}}
|
||||||
|
{{- range $i, $p := $pages -}}
|
||||||
|
{{- if eq $current $p.Params.series_order -}}
|
||||||
|
{{- $idx = add $i 1 -}}
|
||||||
|
{{- $prev = (index $pages (sub $i 1)) | default "" -}}
|
||||||
|
{{- $next = (index $pages (add $i 1)) | default "" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
<div class="series-nav" data-series="{{ $seriesName }}" data-order="{{ $current }}" data-total="{{ $total }}">
|
||||||
|
<div class="series-title">Серия: <a href="{{ $term.Permalink }}">{{ $seriesName }}</a></div>
|
||||||
|
<div class="series-progress">часть {{ $idx }} из {{ $total }}</div>
|
||||||
|
<div class="series-links">
|
||||||
|
{{- if $prev }}<a class="series-prev" href="{{ $prev.Permalink }}">← {{ $prev.Title }}</a>{{- end }}
|
||||||
|
{{- if $next }}<a class="series-next" href="{{ $next.Permalink }}">{{ $next.Title }} →</a>{{- end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Design: Серии постов, переключатель темы справа, соцсети в меню
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Тема `rDedInit` (форк PaperMod) НЕ модифицируется. Все правки — через
|
||||||
|
штатные хуки: корневой `layouts/partials/extend_footer.html` (JS-инжект),
|
||||||
|
корневой `layouts/partials/extend_head.html` (CSS), `assets/css/extended/`,
|
||||||
|
`hugo.toml`.
|
||||||
|
- OpenSpec: change создаётся до правок.
|
||||||
|
|
||||||
|
## 1. Таксономия series
|
||||||
|
|
||||||
|
- `hugo.toml`: `[taxonomies] series = "series"` (в дополнение к
|
||||||
|
`category`/`tag`).
|
||||||
|
- Новый партиал `layouts/partials/series_nav.html`:
|
||||||
|
- принимает страницу, читает `.Params.series` (массив) и `.Params.series_order`;
|
||||||
|
- для первой серии берёт `site.Taxonomies.series` → term → `.Pages`,
|
||||||
|
сортирует по `series_order`, ищет текущий, выводит блок:
|
||||||
|
«Серия: НАЗВАНИЕ · № текущий из N» + ссылки «← Пред.» / «След. →»;
|
||||||
|
- если параметров нет — не выводит ничего.
|
||||||
|
- Подключение: в корневом `layouts/_default/single.html` (override с
|
||||||
|
`{{- define "main" }}`? НЕТ — переопределять весь single.html опасно).
|
||||||
|
Вместо этого: тот же подход, что у PaperMod — вывод БЛОКА серии в начале
|
||||||
|
`.post-content`. В rDedInit single.html НЕ имеет content-хука. Безопасный
|
||||||
|
вариант — **JS-инжект невозможен для контента**, поэтому навешиваем через
|
||||||
|
partial в `single.html`... но это правка темы!
|
||||||
|
|
||||||
|
Обход: PaperMod'ы имеют `layouts/partials/extend_head.html` и
|
||||||
|
`extend_footer.html`, но для контента single.html нет хука. Классический
|
||||||
|
PaperMod-приём: **`assets/scss/extended/` + margin**, нет.
|
||||||
|
Реально: тема rDedInit НЕ поддерживает серии. Добавить можно только:
|
||||||
|
(а) форкнуть single.html в корневой `layouts/_default/single.html`
|
||||||
|
(переопределение — штатный механизм Hugo, тема не трогается), и вставить
|
||||||
|
`{{- partial "series_nav.html" . -}}` перед `.post-content`.
|
||||||
|
Это переопределение будет жить в репозитории сайта и переживёт апдейты темы
|
||||||
|
(требует ручного обновления при изменении темы — приемлемо, тема
|
||||||
|
статична).
|
||||||
|
|
||||||
|
Решение: **переопределить `layouts/_default/single.html`** в корне сайта,
|
||||||
|
скопировав из темы (65 строк) и добавив `{{- partial "series_nav.html" . }}`.
|
||||||
|
|
||||||
|
## 2. Переключатель темы справа
|
||||||
|
|
||||||
|
- `layouts/partials/extend_footer.html` (уже есть) — добавить JS:
|
||||||
|
```js
|
||||||
|
const nav = document.querySelector('.nav');
|
||||||
|
const sw = document.querySelector('.logo .logo-switches');
|
||||||
|
if (nav && sw && !sw.dataset.moved) {
|
||||||
|
sw.dataset.moved = '1';
|
||||||
|
nav.appendChil...[truncated]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Proposal: Серии постов (series), переключатель темы справа, соцсети в меню
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Три улучшения шапки и контента dedinit.ru:
|
||||||
|
|
||||||
|
1. **Таксономия `series`** (как в Blowfish): сгруппировать посты в серии.
|
||||||
|
Статья, помеченная `series`, показывает на странице модуль серии со
|
||||||
|
ссылками на другие части (навигация «Серия: N из M»). Без новых сущностей
|
||||||
|
контента — только taxonomies + вывод.
|
||||||
|
|
||||||
|
2. **Переключатель темы (солнце/луна) — вправо, рядом с меню**. Сейчас он
|
||||||
|
вложен в `.logo` (структура PaperMod) и не перемещается CSS-селектором
|
||||||
|
`.nav > .logo-switches`. Нужен перенос элемента в конец `.nav` (после
|
||||||
|
`#menu`) — через JS-инжект в `extend_footer.html` (штатный хук темы),
|
||||||
|
чтобы не трогать саму тему.
|
||||||
|
|
||||||
|
3. **Иконки соцсетей в верхнем меню**. Стандартные `[params.socialIcons]`
|
||||||
|
(как в Blowfish) рендерятся партиалом `social_icons.html`, но он вызывается
|
||||||
|
только в профиле/на главной. Нужно вывести иконки (github, telegram,
|
||||||
|
mastodon, rss) в шапку рядом с меню — через the same JS-инжект.
|
||||||
|
|
||||||
|
## User-Facing Behavior
|
||||||
|
|
||||||
|
- В `hugo.toml` появляется `[taxonomies] series = "series"`.
|
||||||
|
- Пост с `series: ["Имя"]` и `series_order: N` показывает в начале контента
|
||||||
|
блок «Серия: Имя (N)» со ссылками на предыдущую/следующую часть.
|
||||||
|
- В шапке солнце/луна стоит справа, сразу после пунктов меню.
|
||||||
|
- Рядом с меню — иконки соцсетей (из `[params.socialIcons]`).
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Series and Menu Capability
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Series taxonomy
|
||||||
|
|
||||||
|
Таксономия `series` позволяет группировать посты в серии и показывает на
|
||||||
|
странице поста модуль навигации по серии.
|
||||||
|
|
||||||
|
#### Scenario: Taxonomy enabled
|
||||||
|
- Given hugo.toml has `[taxonomies] series = "series"`
|
||||||
|
- When site is built
|
||||||
|
- Then `/series/` term page exists and posts can be marked with `series`
|
||||||
|
|
||||||
|
#### Scenario: Series module on a post
|
||||||
|
- Given a post with `series: ["Test"]` and `series_order: 2` in front-matter
|
||||||
|
- When that post is rendered
|
||||||
|
- Then a "Серия" block is visible at the top of the content
|
||||||
|
- And it shows the series name, position («2 из N»)
|
||||||
|
- And it links to the previous article (if any) and next article (if any)
|
||||||
|
|
||||||
|
### Requirement: Theme toggle on the right
|
||||||
|
|
||||||
|
Переключатель темы (солнце/луна) отображается в правой части шапки, сразу
|
||||||
|
после пунктов меню.
|
||||||
|
|
||||||
|
#### Scenario: Toggle next to the menu
|
||||||
|
- Given the header markup with `.logo-switches` inside `.logo`
|
||||||
|
- When the page is rendered
|
||||||
|
- Then the theme toggle button is displayed at the right end of `.nav`,
|
||||||
|
after `#menu`
|
||||||
|
|
||||||
|
### Requirement: Social icons in the header
|
||||||
|
|
||||||
|
Иконки соцсетей из `[params.socialIcons]` отображаются в верхнем меню рядом
|
||||||
|
с переключателем темы.
|
||||||
|
|
||||||
|
#### Scenario: Icons rendered from params
|
||||||
|
- Given `[params.socialIcons]` with github and telegram entries
|
||||||
|
- When the page is rendered
|
||||||
|
- Then links with SVG icons appear in the header nav, next to the menu
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Tasks: Series taxonomy, theme toggle right, social icons in menu
|
||||||
|
|
||||||
|
## 1. Таксономия series
|
||||||
|
|
||||||
|
- [x] 1.1 Добавить `[taxonomies] series = "series"` в `hugo.toml`
|
||||||
|
- [x] 1.2 Создать `layouts/partials/series_nav.html` — модуль «Серия: N из M» со
|
||||||
|
ссылками на пред./след. часть (сортировка по `series_order`)
|
||||||
|
- [x] 1.3 Переопределить `layouts/_default/single.html` (копия из темы) и
|
||||||
|
подключить `series_nav.html` перед `.post-content`
|
||||||
|
|
||||||
|
## 2. Переключатель темы справа
|
||||||
|
|
||||||
|
- [x] 2.1 В `layouts/partials/extend_footer.html` добавить JS: перенос
|
||||||
|
`.logo .logo-switches` в конец `.nav` (под флагом, чтобы не дублировать)
|
||||||
|
- [x] 2.2 В `assets/css/extended/typewriter.css` закрепить `.logo-switches`
|
||||||
|
(margin-inline-start) — чтобы встал сразу после `#menu`
|
||||||
|
|
||||||
|
## 3. Соцсети в меню
|
||||||
|
|
||||||
|
- [x] 3.1 Добавить `[params.socialIcons]` в `hugo.toml` (github, telegram, rss)
|
||||||
|
- [x] 3.2 В `extend_footer.html` добавить server-rendered блок иконок
|
||||||
|
(`social_icons` через `svg.html` темы) и JS-перенос в `.nav` рядом с меню
|
||||||
|
|
||||||
|
## 4. Проверка и деплой
|
||||||
|
|
||||||
|
- [x] 4.1 `make build` без ошибок
|
||||||
|
- [x] 4.2 Локальные проверки: series-nav на странице серии (2 поста — prev/next
|
||||||
|
работают), таксономия `/series/` собрана, соцсети в шапке, переключатель
|
||||||
|
переносится JS
|
||||||
|
- [ ] 4.3 `git commit` + push
|
||||||
|
- [ ] 4.4 `make deploy`
|
||||||
Reference in New Issue
Block a user