Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Docs as Сode — подход к работе с текстами, подразумевающий написание текста как кода:
- в простом текстовом редакторе или IDE;
- с использованием системы контроля версий;
- с CI / CD / Code Review.
В настоящее время Docs as Code широко применяется при работе с технической документацией, давая техническим писателям и проектным командам массу удобств и преимуществ.
Но что если пойти дальше, попробовать такой подход не с техническими, а с художественными текстами? Что если автор — не технарь и не айтишник? Просто юный начинающий писатель, который пробует писать прозу и стихи ручкой на бумаге, и надеется познакомить широкую публику со своим творчеством?
В этой статье я расскажу о таком эксперименте (забегая вперед, удачном). Моей дочери 11 лет, она пишет сказки, стихи и рассказы. Чтобы поддержать ее увлечение, я помог ей создать литературный сайт, используя подход Docs as Code. Она успешно освоила основы Markdown и Git. Сейчас она самостоятельно публикует новые произведения и обновляет новости на своем сайте https://lib-beliakova.github.io/.
Продемонстрированные далее файлы и конфиги можно также посмотреть в репозитории https://github.com/lib-beliakova/lib-beliakova.github.io/.
Инструменты
Docs as code — не конкретный набор инструментов, а скорее "философия". Выбор инструментов самый широкий. Недолго думая, я взял то, с чем недавно имел дело по работе (я технический писатель).
- Markdown — язык разметки.
- Git — система контроля версий.
- VS Code — IDE, текстовый редактор.
- MkDocs — генератор статических веб-сайтов из набора Markdown-документов.
- Material for MkDocs — продвинутая тема оформления для MkDocs.
- Docker — контейнеризатор приложений (для упрощения запуска MkDocs).
- GitHub — всем известный веб-сервис для хостинга Git-репозиториев, также предоставляющий бесплатный хостинг статических сайтов GitHub Pages и систему автоматизации сборки GitHub Actions.
Создание проекта MkDocs
Минимальный "проект" MkDocs выглядит так:
.
├─ docs/
│ └─ index.md
└─ mkdocs.yml
Его можно создать командой mkdocs new
или просто добавить необходимые файлы вручную (если MkDocs пока не установлен). Управление параметрами проекта MkDocs осуществляется в конфигурационном файле mkdocs.yaml
в корневой папке проекта. Его первоначальное содержимое может быть таким:
site_name: Литературное творчество Беляковой Анастасии
nav:
- index.md
Этого уже достаточно, чтобы собрать минимальный сайт. Но, перед переходом к сборке, я бы хотел рассмотреть еще несколько предварительных действий.
Подключение темы оформления Material
Нужная нам тема подключается так:
theme:
name: material
Русификация
В шаблоне сайта имеется несколько локализуемых строчек, например "search", "previous", "next". Их можно перевести на русский, добавив плагин i18n
(internationalization) c единственным языком ru
.
plugins:
- i18n:
docs_structure: suffix
default_language: ru
languages:
ru:
name: Russian
build: true
site_name: "Литературное творчество Беляковой Анастасии"
Встроенный поиск
На сайт можно добавить поиск:
plugins:
- search
Настройка локальной сборки сайта в Docker
Пора посмотреть что получилось. Чтобы не засорять ноутбук ребенка MkDocs-ом, Питоном, и прочими зависимостями, засорим его Docker'ом воспользуемся Docker'ом (пусть заодно чуть-чуть с контейнеризацией познакомится). Пишем такой нехитрый Containerfile
:
FROM docker.io/library/python:alpine
WORKDIR /tmp
COPY ./requirements.txt .
RUN apk add --update --no-cache --virtual .build-deps gcc musl-dev &&\
apk add --no-cache git ca-certificates curl &&\
pip install --no-cache-dir --requirement ./requirements.txt &&\
apk del .build-deps &&\
rm ./requirements.txt
WORKDIR /d
CMD ["mkdocs", "serve", "-a", "0.0.0.0:8000"]
Добавляем список зависимостей в файл requirements.txt
:
mkdocs>=1.2.1
mkdocs-material>=7.1.0
mkdocs-static-i18n
Собираем образ:
docker build -t lib-beliakova -f Containerfile .
Запускаем встроенный веб-сервер MkDocs:
docker run --rm -itp 8000:8000 -v "$(pwd):/d" lib-beliakova
Теперь можно увидеть получившийся сайтик в браузере по ссылке http://localhost:8000/. Если изменить любой файл в проекте и сохраниться, MkDocs немедленно подхватит изменения, пересоберет сайт, а страница в браузере сразу обновится. Очень удобно — можно править Markdown-документы, время от времени сохраняться, и сразу видеть результаты в окне браузера.
Наполнение контентом
Тут я уже уступил место за клавиатурой юной писательнице, показав азы Markdown — как добавлять заголовки, разбивать текст на параграфы и вставлять цитаты.
# Иван-дворник и царевна-птица
В одном царстве был, как положено, царь. Ну и конечно же, у этого царя были подданные, крестьяне, земледельцы, ремесленники.
У одного крестьянина было три сына. И младшего звали Иваном.
Жили они бедно, сыновья в школу не ходили и в институт с университетом тоже.
И выросли ребята взрослые. И задумался младший Иван, кем ему работать.
Идёт Иван по улице. Вдруг смотрит -- объявление.
> Срочно требуется царский дворник!
Пришёл Иван к царю на собеседование, спрашивает:
...
Результат: https://lib-beliakova.github.io/tales/Ivan-dvornik/
Также сразу показал команду запуска MkDocs в Docker.
Дочка справилась с перепечатыванием своих бумажных рукописей в markdown без особых затруднений. Оказалось, что это не сложнее работы с Word на школьных уроках информатики. Нужно добавлять по markdown-файлику для каждого произведения в проект в папку docs
, и не забывать "регистрировать" каждый фвйл в mkdoc.yaml
в секции nav
:
nav:
- index.md
- news.md
- Рассказы:
- stories/index.md
- stories/summer.md
- stories/under-our-house.md
- stories/preparation-for-festival.md
- stories/zastroyschiki.md
// ...
- Сказки:
- tales/index.md
- tales/Ivan-dvornik.md
- tales/Masha-rasteryasha.md
- tales/rainbow-lama.md
- Сказки:
- tales/index.md
- tales/Ivan-dvornik.md
- tales/Masha-rasteryasha.md
- tales/rainbow-lama.md
- Фанфики:
- fanfics/index.md
- fanfics/stolen-sun.md
- fanfics/smeshariki.md
// ...
Тут пришлось познакомить юную писательницу с азами YAML, хорошо что VS Code подсвечивает ошибки.
Названия для каждого пункта можно не писать, MkDocs автоматически достает их из Markdown (использует первый заголовок).
У нас "узлы" оглавления ассоциируются с индексными страницами index.md
— это как бы "лендинги", чтобы можно было дать кому-то ссылку на "все рассказы", "все сказки" или "все фанфики". По-умолчанию MkDocs Material так не делает (узлы оглавления просто являются контейнерами документов, не имея своего контента). Чтобы получить нужное поведение, достаточно добавить в конфиг парметр navigation.indexes
:
theme:
name: material
features:
- navigation.indexes
При желании, можно еще включить опцию navigation.expand
(рядом с navigation.indexes
) — в этом случае оглавление будет сразу отображаться в развернутом виде.
Также можно сделать названия, отличные от заголовков внутри файлов. Нам это не потребовалось, но вот пример из документации MkDocs Material:
nav:
- Section:
- section/index.md
- Page 1: section/page-1.md
...
- Page n: section/page-n.md
Публикация в GitHub Pages с использованием GitHub Actions
Инициализируем git-репозиторий, делаем первый commit, закидываем репозиторий на GitHub и подключаем GitHub Pages (тут не буду вдаваться в подробности, все стандартно).
Добавляем файлик .github/workflows /mkdocs.yml
с инструкциями по сборке и деплою сайта для GitHub Actions:
name: mkdocs
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-static-i18n
- run: mkdocs gh-deploy --force
Теперь юная писательница может делать commit и push в VS Code, и через несколько минут изменения автоматически появляются на сайте.
Если скучно ждать, можно следить за процессом в разделе Actions репозитория на GitHub.
Google Analytics
Первые вопросы дочки после публикации сайта — смотрит ли кто-то её творчество? Сколько людей посмотрело? К счастью, в MkDocs есть встроенная поддержка GA, добавить счетчик на сайт можно парой строчек в конфиге:
extra:
analytics:
provider: google
property: G-9ZRQD3E6FD
Разумеется, надо сперва зарегистрировать "property" на https://analytics.google.com/ и подтвердить "права владения" сайтом. Для этого есть несколько вариантов, самым простым мне показалось закинуть сгенерированный Гуглом файлик в корень сайта. Никакие дополнительные "приседания" для этого не нужны. Достаточно положить файл в папку docs
и он автоматически окажется в корневой папке сайта.
Через день-другой в Google Analytics уже можно будет смотреть отчеты.
Форма обратной связи
Ожидаемо, следующий вопрос юной писательницы — нравится ли кому-то её творчество? Вдруг не нравится? Сбор обратной связи можно легко организовать через ту же Google Analytics:
extra:
analytics:
provider: google
property: G-9ZRQD3E6FD
feedback:
title: Вам нравится эта страница?
ratings:
- icon: material/thumb-up-outline
name: Да
data: 1
note: Спасибо! Я рада, что вам понравилось.
- icon: material/thumb-down-outline
name: Нет
data: 0
note: Жаль, что вам не понравилось...
В результате, внизу каждой странички появляется такая разметка:
<form class="md-feedback" name="feedback">
<fieldset>
<legend class="md-feedback__title">
Вам нравится эта страница?
</legend>
<div class="md-feedback__inner">
<div class="md-feedback__list">
<button class="md-feedback__icon md-icon" type="submit" title="Да" data-md-value="1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 9v12H1V9h4m4 12a2 2 0 0 1-2-2V9c0-.55.22-1.05.59-1.41L14.17 1l1.06 1.06c.27.27.44.64.44 1.05l-.03.32L14.69 8H21a2 2 0 0 1 2 2v2c0 .26-.05.5-.14.73l-3.02 7.05C19.54 20.5 18.83 21 18 21H9m0-2h9.03L21 12v-2h-8.79l1.13-5.32L9 9.03V19Z"></path></svg>
</button>
<button class="md-feedback__icon md-icon" type="submit" title="Нет" data-md-value="0">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 15V3h4v12h-4M15 3a2 2 0 0 1 2 2v10c0 .55-.22 1.05-.59 1.41L9.83 23l-1.06-1.06c-.27-.27-.44-.64-.44-1.06l.03-.31.95-4.57H3a2 2 0 0 1-2-2v-2c0-.26.05-.5.14-.73l3.02-7.05C4.46 3.5 5.17 3 6 3h9m0 2H5.97L3 12v2h8.78l-1.13 5.32L15 14.97V5Z"></path></svg>
</button>
</div>
<div class="md-feedback__note">
<div data-md-value="1" hidden="">
Спасибо! Я рада, что вам понравилось.
</div>
<div data-md-value="0" hidden="">
Жаль, что вам не понравилось...
</div>
</div>
</div>
</fieldset>
</form>
В браузере это выглядит и работает так:
Результаты можно смотреть в GA, если добавить отчет по инструкции. Инструкция надежно спрятана в документации MkDocs под спойлером How to visualize the collected feedback ratings :-)
Яндекс Метрика
У веб-аналитики от Яндекса есть замечательная штука, позволяющая подсматривать за действиями отдельных пользователей — Вебвизор. Поэтому, захотелось добавить также и счетчик от Яндекса на сайт. Интересно же посмотреть, как читатели взаимодействуют с текстом, как скроллят, на чем задерживают внимание...
MkDocs не поддерживает аналитику от Яндекса напрямую. Но это значит лишь то, что добавление Яндекса потребует лишь чуть больше телодвижений по сравнению с Google.
Надо лишь создать файл overrides/main.html
и добавить в него код счетчика, выданный Яндексом:
{% extends "base.html" %}
{% block analytics %}
{{ super() }}
<!-- Yandex.Metrika counter - вставить сюда -->
{% endblock %}
И затем добавить папку с "оверрайдами" темы material
в конфиге mkdocs.yaml
:
theme:
name: material
custom_dir: overrides
В результате, счетчик Яндекса появляется на каждой странице сразу после кода счетчика Google (добавленного ранее по-простому).
Социальные кнопки
Их можно добавить так:
extra:
social:
- icon: fontawesome/brands/telegram
link: https://t.me/lib_beliakova
name: Канал в Telegram
- icon: fontawesome/brands/youtube
link: https://www.youtube.com/@a-tunes
name: Канал на YouTube
Кнопки отображаются в футере сайта на каждой странице.
Admonition-блоки (note, warning, info, etc...)
Мы придумали репостить новости с сайта в канале Telegram канал, чтобы читателям было на что подписаться. А чтобы донести это до читателей, добавили вверху на странице новостей заметную ссылку в admonition-блоке:
# Новости
!!! info "Новости в Telegram"
Чтобы не пропускать новости, подпишитесь на [канал сайта в Telegram :fontawesome-brands-telegram:](https://t.me/lib_beliakova).
## 22.02.2023: Издан сборник моих произведений
...
Результат: https://lib-beliakova.github.io/news/
Поддержка admonitions включается в mkdocs.yaml
Markdown-расширением admonition:
markdown_extensions:
- admonition
Такие блоки также можно использовать в художественных целях. Если указать пустой caption блока, получаем просто разноцветные рамочки.
Пёстрая и шумная толпа окружила царевну. Со всех сторон светились и мигали светодиодами вывески, одна зазывающая реклама пыталась перекричать другую.
!!! note ""
Лётный клуб "Волшебный мир": откройте мир с высоты полёта ковра!
!!! info ""
Аптека Кощея Бессмертного: эликсир молодости по доступной цене от отечественного товаропроизводителя!
!!! warning ""
Кафе "Скатерть-Самобранка". Блюдо дня: каша из топора!
!!! tip ""
Гостиница "Избушка на курьих ножках": накормим, напоим и спать уложим!
!!! example ""
Агрокомплекс "У дедки". Наша репка большая-пребольшая!
Результат: https://lib-beliakova.github.io/tales/Masha-rasteryasha/
Admonition еще можно использовать, чтобы спрятать отгадку загадки "под спойлер". Раскрываемый спойлер получается, если начать блок с трех знаков вопроса, вместо трех восклицательных знаков.
Сам вода водицей,
А кипятка боится...
??? info "Отгадка"
Лёд
Результат: https://lib-beliakova.github.io/riddles/
"Ночной" режим
Сейчас многие сайты заботятся о глазках читателей, предоставляя опциональную возможность читать светлый тест на темном фоне. В MkDocs Material это несложно сделать:
theme:
name: material
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-4
name: Темное оформление
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-7
name: Светлое оформление
Результат:
Ссылки-кнопки
MkDocs умеет добавлять "заметные" ссылки, которые выглядят как крупные кнопочки. Например, вот так добавляется кнопка "читать" со значком раскрытой книжки:
[:octicons-book-16: Читать](coast-of-poetry.md){ .md-button }
Результат: https://lib-beliakova.github.io/poems/
В MkDocs "из коробки" доступно более 10000 иконок.
Модификация CSS
Встроенные стили можно легко кастомизить. Для этого надо добавить CSS-файл со своими стилями и подключить его в mkdocs.yaml
:
extra_css:
- extra-styles-v1.css
Номер версии в имени файла — это небольшой хак. При изменениях в стилях можно менять номер, и таким образом избежать "застревания" закешированных старых стилей в браузерах читателей.
Вот так, например, можно настроить "под себя" внешний вид кнопок:
.md-typeset .md-button {
border-radius: 0.3rem;
font-weight: 400;
font-size: 0.7rem;
padding: 0.25em 0.5em;
color: var(--link-color);
}
А так — подтюнить внешний вид ссылок, для "светлой" и "темной" тем оформления:
:root {
--link-color: #115BC0;
}
.md-typeset a,
.md-typeset a:hover {
color: var(--link-color);
}
.md-typeset a:hover:not(.md-button) {
text-decoration: underline;
}
[data-md-color-scheme="slate"] {
--link-color: #58a6ff;
}
Вставка HTML
Вебвизор показал, что мобильные пользователи не всегда находят иконку "гамбургера" для открытия оглавления. Чтобы им помочь, я решил добавить на стартовую страницу дополнительную навигацию. В Markdown можно добавлять фрагменты HTML, которые будут переноситься в итоговый HTML "как есть". Чтобы было красиво, решил попробовать такой подход.
HTML: https://github.com/lib-beliakova/lib-beliakova.github.io/blob/main/docs/index.md?plain=1#L5-L42
CSS: https://github.com/lib-beliakova/lib-beliakova.github.io/blob/main/docs/extra-styles-v1.css#L39-L62
Результат: https://lib-beliakova.github.io/
Прошу не бросаться тапками, это мой чуть ли не первый опыт хоть какой-то верстки (лучше помогите советом). Тем не менее, в вебвизоре видно, что посетители сайта стали этим пользоваться и переходить к другим разделам.
Немного SEO
Файл sitemap позволяет оптимизировать сканирование нашего сайта поисковыми системами. MkDocs автоматически добавляет файл sitemap.xml
в корень сайта. Но, для того чтобы sitemap содержал корректные URLы страниц, нужно добавить в mkdocs.yaml
базовый URL сайта:
site_url: https://lib-beliakova.github.io/
Чтобы поисковики смогли обнаружить наш sitemap, добавляем в папку docs
файл robots.txt
со ссылкой на sitemap.xml
:
User-agent: *
Sitemap: https://lib-beliakova.github.io/sitemap.xml
Особенности использования Markdown в художественных текстах
Пунктуация для прямой речи (тире)
В отличие от технической документации, художественные тексты изобилуют прямой речью. Для правильного выделения прямой речи нужно использовать тире. Чтобы моя юная писательница не заморачивалась каждый раз со вставкой символа, отсутствующего на клавиатуре, я добавил в конфиге расширение smarty:
markdown_extensions:
- smarty
Оно автоматически превращает двойные дефисы в короткое тире (en dash), а тройные — в длинное (em dash).
Пример текста в маркдауне:
# Однажды, под нашим домом...
-- Нинка! Ты не представляешь, что я нашёл!
Я вздрогнула от Серёгиного вопля.
-- Не представляю, как и то, зачем ты так орёшь.
-- Ну хватит нудеть! Пошли покажу!
Результат: https://lib-beliakova.github.io/stories/under-our-house/
"Типографские" кавычки и многоточия
Заодно, расширение smarty превращает "компьютерные" прямые кавычки в "типографские", а троеточия, набранные тремя отдельными точками — в один символ ellipsis. На первый взгляд это кажется ерундой, но по факту здорово облегчает жизнь, если захочется напечатать настоящую книгу в настоящей типографии (спойлер: захотелось, напечатали).
Переводы строк в стихах
Для оформления стихов нужно уметь вставлять переводы строк, не заворачивать же каждую стихотворную строку в отдельный параграф. На первый взгляд, тут непонятно как поступить, ведь одиночный перевод строки в Markdown превращается в пробел в HTML. А двойной перевод строки создает новый параграф <p>
.
Тем не менее, есть три способа добиться получения <br>
в итоговом HTML:
- Закончить строку в Markdown двумя пробелами.
- Закончить строку в Markdown символом
\
(т.е как бы заэкранировать последующий перевод строки). - Просто вставить
<br>
в Markdown.
Мы выбрали первый способ. Хоть он и менее очевидный, но он сохраняет человекочитаемость текста в редакторе.
Пример оформления стиха в Markdown:
Тут я использую скриншот из VS Code, чтобы было видно пробелы, создающие переводы строк.
Результат: https://lib-beliakova.github.io/poems/it-was-autumn/
Иллюстрации
Рисовать в семье никто не умеет. Поэтому выбор пал на художника, о котором все только и говорят последнее время — Midjourney. Десятидолларовой подписки хватило, чтобы сделать полсотни рисунков и проиллюстрировать каждый стишок и рассказик на сайте.
Про работу с Midjourney уже написано множество статей, поэтому не буду вдаваться в детали. Чтобы добиться единого стиля иллюстраций, подходящего к детской литературе, к каждому запросу добавляли "watercolor painting style". Получились как-бы-акварельные рисунки, которые потом и для "физической" книги пригодились.
Аудио
Возникла идея сделать аудиосказки, чтобы к читателям добавились также слушатели. В Markdown нет аналога тега <audio>
, поэтому для вставки плеера пришлось использовать чистый HTML: https://github.com/lib-beliakova/lib-beliakova.github.io/blob/main/docs/tales/Masha-rasteryasha.md?plain=1#L3-L9
Результат: https://lib-beliakova.github.io/tales/Masha-rasteryasha/
В этом примере плеер "завернут" в нестандартный admоnition типа "audio". Это, разумеется, не важно для работы плеера. Сделано чисто из эстетических соображений, чтобы у плеера была рамочка, подпись и иконка с наушниками. Не буду здесь рассказывать про реализацию дополнительного типа admoinition, чтобы не растягивать и без того длинную статью. Если кому интересно, пишите, расскажу в комментариях.
Заключение
Оказалось, что "нетехнический" писатель тоже может эффективно использовать Docs as Code для быстрой публикации своих произведений. В то же время, многие технические писатели "старой закалки" с осторожностью присматриваются к этому подходу. Смелее, ничего страшного тут нет, даже ребенок справляется :-) Я уже давно не помогаю и не стою на подхвате. Добавляет новый файл, сохраняет, затем коммит, пуш… И готово, новый контент в продакшне!
Также хочу передать благодарность Владиславу Himura за то, что в свое время познакомил с MkDocs.