vault backup: 2025-08-04 15:36:18
This commit is contained in:
@@ -1,764 +0,0 @@
|
|||||||
# Hugo миграция и настройка
|
|
||||||
|
|
||||||
## 1. Миграция контента
|
|
||||||
|
|
||||||
### 1.1 Структура контента
|
|
||||||
|
|
||||||
**Текущая структура (Quartz):**
|
|
||||||
```
|
|
||||||
content/
|
|
||||||
├── notes/
|
|
||||||
│ ├── Идеи/
|
|
||||||
│ ├── Мой сервер/
|
|
||||||
│ └── index.md
|
|
||||||
├── daily/
|
|
||||||
└── templates/
|
|
||||||
```
|
|
||||||
|
|
||||||
**Новая структура (Hugo):**
|
|
||||||
```
|
|
||||||
content/
|
|
||||||
├── notes/
|
|
||||||
│ ├── идеи/
|
|
||||||
│ │ ├── obsidian-telegram-bot/
|
|
||||||
│ │ │ ├── mvp-telegram-bota.md
|
|
||||||
│ │ │ └── telegram-bot-dlya-obsidian.md
|
|
||||||
│ │ └── pereezd-na-hugo/
|
|
||||||
│ │ ├── plan-pereezda-na-hugo.md
|
|
||||||
│ │ ├── webhook-server-na-go.md
|
|
||||||
│ │ └── hugo-migratsiya-i-nastrojka.md
|
|
||||||
│ ├── мой-сервер/
|
|
||||||
│ │ ├── authelia-authentication/
|
|
||||||
│ │ ├── git-service/
|
|
||||||
│ │ ├── second-mind-setup/
|
|
||||||
│ │ └── traefik-reverse-proxy/
|
|
||||||
│ └── _index.md
|
|
||||||
├── daily/
|
|
||||||
│ └── _index.md
|
|
||||||
└── templates/
|
|
||||||
└── _index.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Скрипт миграции
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
# scripts/migrate_content.py
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def slugify(text):
|
|
||||||
"""Преобразование текста в slug для URL"""
|
|
||||||
text = text.lower()
|
|
||||||
text = re.sub(r'[^\w\s-]', '', text)
|
|
||||||
text = re.sub(r'[-\s]+', '-', text)
|
|
||||||
return text.strip('-')
|
|
||||||
|
|
||||||
def migrate_content():
|
|
||||||
"""Миграция контента из Quartz в Hugo"""
|
|
||||||
|
|
||||||
# Пути
|
|
||||||
quartz_content = "quartz/content"
|
|
||||||
hugo_content = "hugo-site/content"
|
|
||||||
|
|
||||||
# Создание структуры Hugo
|
|
||||||
os.makedirs(hugo_content, exist_ok=True)
|
|
||||||
|
|
||||||
# Обработка каждой директории
|
|
||||||
for root, dirs, files in os.walk(quartz_content):
|
|
||||||
# Пропускаем служебные директории
|
|
||||||
if any(skip in root for skip in ['.obsidian', '.git', '__pycache__']):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Определяем относительный путь
|
|
||||||
rel_path = os.path.relpath(root, quartz_content)
|
|
||||||
|
|
||||||
# Создаем соответствующую структуру в Hugo
|
|
||||||
hugo_path = os.path.join(hugo_content, rel_path)
|
|
||||||
os.makedirs(hugo_path, exist_ok=True)
|
|
||||||
|
|
||||||
# Обрабатываем файлы
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.md'):
|
|
||||||
migrate_markdown_file(
|
|
||||||
os.path.join(root, file),
|
|
||||||
os.path.join(hugo_path, file)
|
|
||||||
)
|
|
||||||
|
|
||||||
def migrate_markdown_file(src_path, dst_path):
|
|
||||||
"""Миграция отдельного Markdown файла"""
|
|
||||||
|
|
||||||
with open(src_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Обработка frontmatter
|
|
||||||
content = process_frontmatter(content)
|
|
||||||
|
|
||||||
# Обработка внутренних ссылок
|
|
||||||
content = process_internal_links(content)
|
|
||||||
|
|
||||||
# Обработка изображений
|
|
||||||
content = process_images(content)
|
|
||||||
|
|
||||||
# Сохранение файла
|
|
||||||
with open(dst_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
def process_frontmatter(content):
|
|
||||||
"""Обработка YAML frontmatter для Hugo"""
|
|
||||||
|
|
||||||
# Проверяем наличие frontmatter
|
|
||||||
if content.startswith('---'):
|
|
||||||
# Извлекаем frontmatter
|
|
||||||
parts = content.split('---', 2)
|
|
||||||
if len(parts) >= 3:
|
|
||||||
frontmatter = parts[1]
|
|
||||||
body = parts[2]
|
|
||||||
|
|
||||||
# Добавляем Hugo-специфичные поля
|
|
||||||
frontmatter = add_hugo_frontmatter(frontmatter)
|
|
||||||
|
|
||||||
return f"---\n{frontmatter}\n---\n{body}"
|
|
||||||
|
|
||||||
# Если frontmatter отсутствует, создаем базовый
|
|
||||||
title = extract_title_from_content(content)
|
|
||||||
return f"""---
|
|
||||||
title: "{title}"
|
|
||||||
date: {get_current_date()}
|
|
||||||
draft: false
|
|
||||||
---
|
|
||||||
|
|
||||||
{content}"""
|
|
||||||
|
|
||||||
def add_hugo_frontmatter(frontmatter):
|
|
||||||
"""Добавление Hugo-специфичных полей в frontmatter"""
|
|
||||||
|
|
||||||
# Добавляем базовые поля если их нет
|
|
||||||
if 'draft' not in frontmatter:
|
|
||||||
frontmatter += '\ndraft: false'
|
|
||||||
|
|
||||||
if 'date' not in frontmatter:
|
|
||||||
frontmatter += f'\ndate: {get_current_date()}'
|
|
||||||
|
|
||||||
return frontmatter
|
|
||||||
|
|
||||||
def process_internal_links(content):
|
|
||||||
"""Обработка внутренних ссылок для Hugo"""
|
|
||||||
|
|
||||||
# Заменяем [[wiki links]] на Hugo ссылки
|
|
||||||
content = re.sub(r'\[\[([^\]]+)\]\]', r'[{{< ref "\1" >}}]', content)
|
|
||||||
|
|
||||||
# Обрабатываем обычные ссылки
|
|
||||||
content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', process_link, content)
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
def process_link(match):
|
|
||||||
"""Обработка ссылок"""
|
|
||||||
text = match.group(1)
|
|
||||||
url = match.group(2)
|
|
||||||
|
|
||||||
# Если это внутренняя ссылка на .md файл
|
|
||||||
if url.endswith('.md'):
|
|
||||||
# Преобразуем в Hugo ссылку
|
|
||||||
slug = slugify(url.replace('.md', ''))
|
|
||||||
return f'[{text}]({{{{< ref "{slug}" >}}}})'
|
|
||||||
|
|
||||||
return match.group(0)
|
|
||||||
|
|
||||||
def process_images(content):
|
|
||||||
"""Обработка изображений"""
|
|
||||||
|
|
||||||
# Перемещаем изображения в static директорию
|
|
||||||
# и обновляем ссылки
|
|
||||||
content = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', process_image, content)
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
def process_image(match):
|
|
||||||
"""Обработка изображений"""
|
|
||||||
alt = match.group(1)
|
|
||||||
src = match.group(2)
|
|
||||||
|
|
||||||
# Если изображение локальное, перемещаем в static
|
|
||||||
if not src.startswith(('http://', 'https://')):
|
|
||||||
# Копируем изображение в static/images
|
|
||||||
static_path = f"static/images/{os.path.basename(src)}"
|
|
||||||
if os.path.exists(src):
|
|
||||||
shutil.copy2(src, static_path)
|
|
||||||
|
|
||||||
# Обновляем ссылку
|
|
||||||
src = f"/images/{os.path.basename(src)}"
|
|
||||||
|
|
||||||
return f''
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
migrate_content()
|
|
||||||
print("Миграция контента завершена!")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Настройка Hugo
|
|
||||||
|
|
||||||
### 2.1 Базовая конфигурация
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# config.toml
|
|
||||||
baseURL = "https://aepif.ru"
|
|
||||||
languageCode = "ru-ru"
|
|
||||||
title = "Second Mind"
|
|
||||||
theme = "custom-theme"
|
|
||||||
|
|
||||||
[params]
|
|
||||||
description = "Персональная база знаний и заметки"
|
|
||||||
author = "AEP"
|
|
||||||
github = "https://github.com/username"
|
|
||||||
|
|
||||||
# Настройки поиска
|
|
||||||
enableSearch = true
|
|
||||||
searchResultsLength = 10
|
|
||||||
|
|
||||||
# Настройки навигации
|
|
||||||
showBreadcrumb = true
|
|
||||||
showReadingTime = true
|
|
||||||
showWordCount = true
|
|
||||||
|
|
||||||
[menu]
|
|
||||||
[[menu.main]]
|
|
||||||
identifier = "notes"
|
|
||||||
name = "Заметки"
|
|
||||||
url = "/notes/"
|
|
||||||
weight = 1
|
|
||||||
|
|
||||||
[[menu.main]]
|
|
||||||
identifier = "daily"
|
|
||||||
name = "Дневник"
|
|
||||||
url = "/daily/"
|
|
||||||
weight = 2
|
|
||||||
|
|
||||||
[[menu.main]]
|
|
||||||
identifier = "templates"
|
|
||||||
name = "Шаблоны"
|
|
||||||
url = "/templates/"
|
|
||||||
weight = 3
|
|
||||||
|
|
||||||
[markup]
|
|
||||||
[markup.goldmark]
|
|
||||||
[markup.goldmark.renderer]
|
|
||||||
unsafe = true
|
|
||||||
|
|
||||||
[markup.highlight]
|
|
||||||
style = "dracula"
|
|
||||||
lineNos = true
|
|
||||||
lineNumbersInTable = true
|
|
||||||
|
|
||||||
[outputs]
|
|
||||||
home = ["HTML", "RSS", "JSON"]
|
|
||||||
|
|
||||||
[outputFormats]
|
|
||||||
[outputFormats.JSON]
|
|
||||||
mediaType = "application/json"
|
|
||||||
baseName = "index"
|
|
||||||
isPlainText = true
|
|
||||||
notAlternative = true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Структура темы
|
|
||||||
|
|
||||||
```
|
|
||||||
themes/custom-theme/
|
|
||||||
├── assets/
|
|
||||||
│ ├── css/
|
|
||||||
│ │ ├── main.scss
|
|
||||||
│ │ └── _variables.scss
|
|
||||||
│ ├── js/
|
|
||||||
│ │ └── main.js
|
|
||||||
│ └── images/
|
|
||||||
├── layouts/
|
|
||||||
│ ├── _default/
|
|
||||||
│ │ ├── baseof.html
|
|
||||||
│ │ ├── list.html
|
|
||||||
│ │ └── single.html
|
|
||||||
│ ├── partials/
|
|
||||||
│ │ ├── head.html
|
|
||||||
│ │ ├── header.html
|
|
||||||
│ │ ├── footer.html
|
|
||||||
│ │ ├── navigation.html
|
|
||||||
│ │ ├── search.html
|
|
||||||
│ │ └── breadcrumb.html
|
|
||||||
│ ├── shortcodes/
|
|
||||||
│ │ ├── note.html
|
|
||||||
│ │ └── warning.html
|
|
||||||
│ └── index.html
|
|
||||||
├── static/
|
|
||||||
│ ├── css/
|
|
||||||
│ ├── js/
|
|
||||||
│ └── images/
|
|
||||||
└── theme.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 Базовая тема
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- layouts/_default/baseof.html -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ .Site.LanguageCode }}">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>
|
|
||||||
|
|
||||||
{{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify }}
|
|
||||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
|
||||||
|
|
||||||
{{ partial "head.html" . }}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{ partial "header.html" . }}
|
|
||||||
|
|
||||||
<main class="container">
|
|
||||||
{{ block "main" . }}{{ end }}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
{{ partial "footer.html" . }}
|
|
||||||
|
|
||||||
{{ $script := resources.Get "js/main.js" | resources.Minify }}
|
|
||||||
<script src="{{ $script.RelPermalink }}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- layouts/_default/single.html -->
|
|
||||||
{{ define "main" }}
|
|
||||||
<article class="note">
|
|
||||||
<header class="note-header">
|
|
||||||
<h1>{{ .Title }}</h1>
|
|
||||||
{{ if .Params.date }}
|
|
||||||
<time datetime="{{ .Date.Format "2006-01-02" }}">
|
|
||||||
{{ .Date.Format "2 January 2006" }}
|
|
||||||
</time>
|
|
||||||
{{ end }}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{{ partial "breadcrumb.html" . }}
|
|
||||||
|
|
||||||
<div class="note-content">
|
|
||||||
{{ .Content }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ if .Params.tags }}
|
|
||||||
<footer class="note-footer">
|
|
||||||
<div class="tags">
|
|
||||||
{{ range .Params.tags }}
|
|
||||||
<a href="{{ "tags/" | relLangURL }}{{ . | urlize }}" class="tag">{{ . }}</a>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
{{ end }}
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 Стили
|
|
||||||
|
|
||||||
```scss
|
|
||||||
// assets/css/main.scss
|
|
||||||
@import "variables";
|
|
||||||
|
|
||||||
// Базовые стили
|
|
||||||
body {
|
|
||||||
font-family: $font-family-base;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: $text-color;
|
|
||||||
background-color: $bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: $container-width;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 $spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Заметки
|
|
||||||
.note {
|
|
||||||
background: $note-bg;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: $spacing * 2;
|
|
||||||
margin-bottom: $spacing * 2;
|
|
||||||
box-shadow: $box-shadow;
|
|
||||||
|
|
||||||
&-header {
|
|
||||||
border-bottom: 1px solid $border-color;
|
|
||||||
padding-bottom: $spacing;
|
|
||||||
margin-bottom: $spacing * 2;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 $spacing 0;
|
|
||||||
color: $heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
time {
|
|
||||||
color: $muted-color;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 1.7;
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
color: $heading-color;
|
|
||||||
margin-top: $spacing * 2;
|
|
||||||
margin-bottom: $spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: $spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: $code-bg;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: $code-bg;
|
|
||||||
padding: $spacing;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
overflow-x: auto;
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Навигация
|
|
||||||
.navigation {
|
|
||||||
background: $nav-bg;
|
|
||||||
padding: $spacing;
|
|
||||||
margin-bottom: $spacing * 2;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
gap: $spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $nav-link-color;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $nav-link-hover-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Поиск
|
|
||||||
.search {
|
|
||||||
margin-bottom: $spacing * 2;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: $spacing;
|
|
||||||
border: 1px solid $border-color;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
font-size: 1em;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $primary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Хлебные крошки
|
|
||||||
.breadcrumb {
|
|
||||||
margin-bottom: $spacing;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: $muted-color;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $link-color;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Оптимизации
|
|
||||||
|
|
||||||
### 3.1 Производительность
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# config.toml (дополнительные настройки)
|
|
||||||
[build]
|
|
||||||
writeStats = true
|
|
||||||
|
|
||||||
[imaging]
|
|
||||||
quality = 85
|
|
||||||
resampleFilter = "Lanczos"
|
|
||||||
|
|
||||||
[minify]
|
|
||||||
[minify.tdewolff]
|
|
||||||
[minify.tdewolff.css]
|
|
||||||
keepCSS2 = true
|
|
||||||
precision = 0
|
|
||||||
[minify.tdewolff.html]
|
|
||||||
keepConditionalComments = true
|
|
||||||
keepDefaultDoctype = true
|
|
||||||
keepDocumentTags = true
|
|
||||||
keepEndTags = true
|
|
||||||
keepQuotes = false
|
|
||||||
keepWhitespace = false
|
|
||||||
[minify.tdewolff.js]
|
|
||||||
keepVarNames = false
|
|
||||||
precision = 0
|
|
||||||
[minify.tdewolff.json]
|
|
||||||
keepNumbers = false
|
|
||||||
precision = 0
|
|
||||||
[minify.tdewolff.svg]
|
|
||||||
keepComments = false
|
|
||||||
[minify.tdewolff.xml]
|
|
||||||
keepWhitespace = false
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 SEO оптимизация
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- layouts/partials/head.html -->
|
|
||||||
{{ if .IsHome }}
|
|
||||||
<meta name="description" content="{{ .Site.Params.description }}">
|
|
||||||
{{ else }}
|
|
||||||
<meta name="description" content="{{ .Summary }}">
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<meta name="author" content="{{ .Site.Params.author }}">
|
|
||||||
<meta name="robots" content="index, follow">
|
|
||||||
|
|
||||||
<!-- Open Graph -->
|
|
||||||
<meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }}{{ end }}">
|
|
||||||
<meta property="og:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary }}{{ end }}">
|
|
||||||
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}">
|
|
||||||
<meta property="og:url" content="{{ .Permalink }}">
|
|
||||||
|
|
||||||
<!-- Twitter Card -->
|
|
||||||
<meta name="twitter:card" content="summary">
|
|
||||||
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }}{{ end }}">
|
|
||||||
<meta name="twitter:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary }}{{ end }}">
|
|
||||||
|
|
||||||
<!-- Canonical URL -->
|
|
||||||
<link rel="canonical" href="{{ .Permalink }}">
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Поиск
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// assets/js/search.js
|
|
||||||
class Search {
|
|
||||||
constructor() {
|
|
||||||
this.searchIndex = null;
|
|
||||||
this.searchInput = document.getElementById('search-input');
|
|
||||||
this.searchResults = document.getElementById('search-results');
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
await this.loadSearchIndex();
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadSearchIndex() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/index.json');
|
|
||||||
this.searchIndex = await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load search index:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
|
||||||
this.searchInput.addEventListener('input', (e) => {
|
|
||||||
this.search(e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
search(query) {
|
|
||||||
if (!this.searchIndex || !query.trim()) {
|
|
||||||
this.hideResults();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = this.searchIndex.filter(item => {
|
|
||||||
const searchText = `${item.title} ${item.content}`.toLowerCase();
|
|
||||||
return searchText.includes(query.toLowerCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
this.displayResults(results.slice(0, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
displayResults(results) {
|
|
||||||
if (results.length === 0) {
|
|
||||||
this.searchResults.innerHTML = '<p>Ничего не найдено</p>';
|
|
||||||
} else {
|
|
||||||
const html = results.map(item => `
|
|
||||||
<div class="search-result">
|
|
||||||
<h3><a href="${item.url}">${item.title}</a></h3>
|
|
||||||
<p>${item.content.substring(0, 150)}...</p>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
this.searchResults.innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
showResults() {
|
|
||||||
this.searchResults.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
hideResults() {
|
|
||||||
this.searchResults.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация поиска
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
new Search();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Тестирование
|
|
||||||
|
|
||||||
### 4.1 Локальная разработка
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# scripts/dev.sh
|
|
||||||
|
|
||||||
echo "Запуск Hugo в режиме разработки..."
|
|
||||||
|
|
||||||
# Установка зависимостей
|
|
||||||
hugo mod get
|
|
||||||
|
|
||||||
# Запуск сервера разработки
|
|
||||||
hugo server \
|
|
||||||
--bind 0.0.0.0 \
|
|
||||||
--port 1313 \
|
|
||||||
--disableFastRender \
|
|
||||||
--noHTTPCache \
|
|
||||||
--buildDrafts \
|
|
||||||
--buildFuture
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Тестирование сборки
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# scripts/test-build.sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Тестирование сборки Hugo..."
|
|
||||||
|
|
||||||
# Очистка предыдущей сборки
|
|
||||||
rm -rf public/
|
|
||||||
|
|
||||||
# Сборка сайта
|
|
||||||
hugo --minify
|
|
||||||
|
|
||||||
# Проверка размера
|
|
||||||
echo "Размер сборки:"
|
|
||||||
du -sh public/
|
|
||||||
|
|
||||||
# Проверка количества страниц
|
|
||||||
echo "Количество страниц:"
|
|
||||||
find public/ -name "*.html" | wc -l
|
|
||||||
|
|
||||||
echo "Сборка завершена успешно!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Развертывание
|
|
||||||
|
|
||||||
### 5.1 Скрипт сборки для production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# scripts/build-production.sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Сборка Hugo для production..."
|
|
||||||
|
|
||||||
# Очистка
|
|
||||||
rm -rf public/
|
|
||||||
|
|
||||||
# Сборка с оптимизациями
|
|
||||||
hugo \
|
|
||||||
--minify \
|
|
||||||
--gc \
|
|
||||||
--cleanDestinationDir \
|
|
||||||
--environment production
|
|
||||||
|
|
||||||
# Оптимизация изображений
|
|
||||||
find public/ -name "*.jpg" -o -name "*.png" | xargs -I {} convert {} -strip -quality 85 {}
|
|
||||||
|
|
||||||
# Сжатие статических файлов
|
|
||||||
find public/ -name "*.css" -o -name "*.js" | xargs gzip -9
|
|
||||||
|
|
||||||
echo "Production сборка завершена!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 Nginx конфигурация
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# configs/nginx.conf
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name aepif.ru www.aepif.ru;
|
|
||||||
|
|
||||||
root /var/www/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Gzip сжатие
|
|
||||||
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;
|
|
||||||
|
|
||||||
# Кэширование статических файлов
|
|
||||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Обработка Hugo страниц
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ $uri.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Обработка 404 ошибок
|
|
||||||
error_page 404 /404.html;
|
|
||||||
|
|
||||||
# Безопасность
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
136
Идеи/Оптимизация ресурсов VPS/Миграция контента на Hugo.md
Normal file
136
Идеи/Оптимизация ресурсов VPS/Миграция контента на Hugo.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Миграция контента на Hugo
|
||||||
|
|
||||||
|
## 1. Миграция контента
|
||||||
|
|
||||||
|
### 1.1 Структура контента
|
||||||
|
|
||||||
|
**Текущая структура (Quartz):**
|
||||||
|
```
|
||||||
|
content/
|
||||||
|
├── notes/
|
||||||
|
│ ├── Идеи/
|
||||||
|
│ ├── Мой сервер/
|
||||||
|
│ └── index.md
|
||||||
|
├── daily/
|
||||||
|
└── templates/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Новая структура (Hugo):**
|
||||||
|
```
|
||||||
|
content/
|
||||||
|
├── notes/
|
||||||
|
│ ├── идеи/
|
||||||
|
│ │ ├── obsidian-telegram-bot/
|
||||||
|
│ │ └── optimizatsiya-resursov-vps/
|
||||||
|
│ ├── мой-сервер/
|
||||||
|
│ └── _index.md
|
||||||
|
├── daily/
|
||||||
|
│ └── _index.md
|
||||||
|
└── templates/
|
||||||
|
└── _index.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Основные изменения при миграции
|
||||||
|
|
||||||
|
**Frontmatter:**
|
||||||
|
- Добавление Hugo-специфичных полей
|
||||||
|
- Преобразование дат в стандартный формат
|
||||||
|
- Добавление метаданных для SEO
|
||||||
|
|
||||||
|
**Внутренние ссылки:**
|
||||||
|
- Замена `[[wiki links]]` на Hugo ссылки
|
||||||
|
- Обновление путей к файлам
|
||||||
|
- Обработка относительных ссылок
|
||||||
|
|
||||||
|
**Изображения:**
|
||||||
|
- Перемещение в `static/images/`
|
||||||
|
- Обновление путей в контенте
|
||||||
|
- Оптимизация размера файлов
|
||||||
|
|
||||||
|
## 2. Базовая настройка Hugo
|
||||||
|
|
||||||
|
### 2.1 Конфигурация
|
||||||
|
|
||||||
|
**Основные параметры:**
|
||||||
|
- `baseURL` - адрес сайта
|
||||||
|
- `languageCode` - язык контента
|
||||||
|
- `title` - название сайта
|
||||||
|
- `theme` - используемая тема
|
||||||
|
|
||||||
|
**Настройки контента:**
|
||||||
|
- Структура меню
|
||||||
|
- Параметры поиска
|
||||||
|
- Настройки навигации
|
||||||
|
- SEO параметры
|
||||||
|
|
||||||
|
### 2.2 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
hugo-site/
|
||||||
|
├── config.toml # Основная конфигурация
|
||||||
|
├── content/ # Контент сайта
|
||||||
|
├── layouts/ # Шаблоны темы
|
||||||
|
├── static/ # Статические файлы
|
||||||
|
├── themes/ # Темы
|
||||||
|
└── public/ # Собранный сайт
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Оптимизации
|
||||||
|
|
||||||
|
### 3.1 Производительность
|
||||||
|
|
||||||
|
**Сборка:**
|
||||||
|
- Минификация CSS/JS
|
||||||
|
- Оптимизация изображений
|
||||||
|
- Сжатие статических файлов
|
||||||
|
- Кэширование ресурсов
|
||||||
|
|
||||||
|
**SEO:**
|
||||||
|
- Мета-теги
|
||||||
|
- Open Graph разметка
|
||||||
|
- Sitemap генерация
|
||||||
|
- Canonical URLs
|
||||||
|
|
||||||
|
### 3.2 Поиск
|
||||||
|
|
||||||
|
**Функциональность:**
|
||||||
|
- Поиск по контенту
|
||||||
|
- Фильтрация результатов
|
||||||
|
- Подсветка найденного текста
|
||||||
|
- Быстрый поиск
|
||||||
|
|
||||||
|
## 4. Развертывание
|
||||||
|
|
||||||
|
### 4.1 Локальная разработка
|
||||||
|
|
||||||
|
**Команды:**
|
||||||
|
- `hugo server` - запуск сервера разработки
|
||||||
|
- `hugo --minify` - сборка для production
|
||||||
|
- `hugo --gc` - очистка неиспользуемых файлов
|
||||||
|
|
||||||
|
### 4.2 Production
|
||||||
|
|
||||||
|
**Оптимизации:**
|
||||||
|
- Минификация всех ресурсов
|
||||||
|
- Оптимизация изображений
|
||||||
|
- Gzip сжатие
|
||||||
|
- Кэширование статических файлов
|
||||||
|
|
||||||
|
## 5. Преимущества миграции
|
||||||
|
|
||||||
|
### 5.1 Производительность
|
||||||
|
- **Время сборки**: С 30-60 секунд до 5-10 секунд
|
||||||
|
- **Размер файлов**: Снижение на 40-60%
|
||||||
|
- **Время загрузки**: Улучшение на 50-70%
|
||||||
|
|
||||||
|
### 5.2 Операционные
|
||||||
|
- **Простота развертывания**: Один бинарник
|
||||||
|
- **Низкое потребление ресурсов**: Минимальные требования
|
||||||
|
- **Надежность**: Меньше зависимостей
|
||||||
|
- **Безопасность**: Меньше уязвимостей
|
||||||
|
|
||||||
|
### 5.3 Функциональность
|
||||||
|
- **SEO оптимизация**: Встроенные возможности
|
||||||
|
- **Адаптивность**: Современные темы
|
||||||
|
- **Поиск**: Быстрый и точный
|
||||||
|
- **Кастомизация**: Полный контроль
|
||||||
Reference in New Issue
Block a user