No description
Find a file
2026-08-10 00:53:23 +00:00
docslib Bump version to 0.2.0 and refine mobile dock 2026-08-10 00:53:23 +00:00
examples Initial SharedDocsLib implementation 2026-08-09 17:59:49 +00:00
tests Bump version to 0.2.0 and refine mobile dock 2026-08-10 00:53:23 +00:00
.gitignore Initial SharedDocsLib implementation 2026-08-09 17:59:49 +00:00
LICENSE Initial SharedDocsLib implementation 2026-08-09 17:59:49 +00:00
pyproject.toml Bump version to 0.2.0 and refine mobile dock 2026-08-10 00:53:23 +00:00
README.md Add static documentation build API 2026-08-10 00:28:38 +00:00
uv.lock Bump version to 0.2.0 and refine mobile dock 2026-08-10 00:53:23 +00:00

SharedDocsLib

SharedDocsLib — небольшой Python-фреймворк для документации. Страницы и HTML-компоненты описываются функциями с декораторами, а библиотека собирает Obsidian-подобный автономный клиент и отдаёт его через FastAPI.

Возможности

  • страницы с номерами 1, 1.1, 1.1.2 и автоматическим деревом;
  • собственные @Component и набор безопасных базовых компонентов;
  • поиск по всему контенту, светлая/тёмная тема, breadcrumbs, Previous/Next;
  • изображения из assets_dir, единожды закодированные в base64 внутри manifest;
  • полностью автономный index.html: CSS, JavaScript и начальный manifest встроены;
  • хранение manifest в IndexedDB и одна проверка обновления при загрузке;
  • index.zip, содержащий только index.html;
  • live-preview с перекомпиляцией изменившегося Python-модуля.

UI не содержит языко-зависимых служебных подписей. Базовый шрифт — system-ui 16px; служебные UTF-8-пиктограммы используют Arial, а навигационные стрелки остаются в system-ui. AMOLED-тема включена по умолчанию и сохраняется локально после переключения.

На десктопе компактный header содержит полное название документации и search/theme/refresh, а слева постоянно видны header-кнопки и дерево страниц. Поисковый placeholder медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 5 секунд; при вводе поле раскрывает ранжированные результаты вниз. Кнопка вращается во время проверки manifest; при наведении она заменяет поиск и тему, показывая ISO-дату и короткий revision, а при нажатии синхронизирует manifest и перезагружает страницу.

На мобильных устройствах используется viewport 100dvh и нижний dock. Кнопка заменяет содержимое страницы копией desktop-sidebar. Ввод в нижний поиск заменяет содержимое результатами, расположенными снизу вверх по релевантности. Тема и обновление на мобильном скрыты.

Установка из Git

uv venv
source .venv/bin/activate
uv pip install "git+https://git.snw.su/snw/SharedDocsLib.git@main"

Для разработки и запуска примера из клонированного репозитория:

uv sync --extra dev
uv run python examples/basic.py

После запуска доступны:

  • GET http://127.0.0.1:1234/manifest.json
  • GET http://127.0.0.1:1234/index.html
  • GET http://127.0.0.1:1234/index.zip
  • GET http://127.0.0.1:1234/live (только при live_preview=True)

Минимальный пример

from docslib import HeaderButton, run
from docslib.hooks import Component, Page
from docslib.components import H1, Image, LocalLink, P

@Component
def Lead(text):
    return f'<p class="lead">{text}</p>'

@Page("1.", "Page title")
def page1():
    return [
        H1("Hello"),
        P("Documentation text"),
        Image("photo.png", "Description"),
        LocalLink("Open nested page", subpage1),
    ]

@Page("1.1", "Sub-page")
def subpage1():
    return [H1("Nested page")]

if __name__ == "__main__":
    run(
        port=1234,
        host="0.0.0.0",
        title="My docs",
        assets_dir="./assets",
        custom_css="./assets/inject.css",
        manifest_path="http://127.0.0.1:1234/",
        live_preview=True,
        header_buttons=[
            HeaderButton("Знакомство", page1),
            HeaderButton("API reference", subpage1),
            HeaderButton.external("Project", "https://example.com")
        ],
    )

manifest_path принимает полный путь к JSON либо базовый URL, к которому будет добавлен manifest.json. Для совместимости также принят вариант manifest_parh из первоначального API, но в новом коде лучше использовать правильное имя.

Статическая сборка

build принимает параметры компиляции из run, не связанные с сервером, и создаёт готовый статический комплект. По умолчанию используется каталог ./build; отсутствующие родительские каталоги создаются автоматически.

from docslib import HeaderButton, build

build(
    build_directory="./dist/docs",
    title="My docs",
    assets_dir="./assets",
    custom_css="./assets/inject.css",
    manifest_path="./manifest.json",
    header_buttons=[
        HeaderButton("Знакомство", page1),
        HeaderButton("API reference", subpage1),
    ],
)

В build_directory записываются ровно три файла:

  • index.html — полностью автономный клиент;
  • manifest.json — manifest для публикации отдельным файлом;
  • index.zip — архив, содержащий только index.html.

Функция возвращает BuildResult с теми же данными в памяти. Серверные параметры host, port, live_preview, cors_origins и log_level ей не требуются.

Компоненты

Доступны H1, H2, H3, P, MarkdownText, Image, Link, LocalLink (LocLink), Code, InlineCode, Quote, Callout, List, Table, Divider, Badge, Details, Json и RawHTML.

Обычные компоненты экранируют пользовательский текст. RawHTML и HTML из собственных @Component считаются доверенными и вставляются как есть.

Функция страницы может вернуть строку, список/генератор строк, вложенные списки или None.

Ссылки между страницами

LocalLink создаёт обычную браузерную ссылку с hash-route. Поэтому ссылку можно копировать, открывать в новой вкладке и активировать с клавиатуры. Клиент перехватывает только обычный клик и показывает нужную страницу без перезагрузки.

# По объекту функции страницы — предпочтительный вариант:
LocalLink("Установка", install_page)

# По точному номеру:
LocalLink("Установка", "1.2")

# По уникальному названию:
LocalLink("Установка", "Install")

# По названию внутри раздела, если названия повторяются:
LocalLink("Установка для сервера", "Install", section="2")

Цель по функции разрешается во время компиляции, когда все @Page уже зарегистрированы, поэтому можно ссылаться и на функцию, объявленную ниже по файлу. Если название неоднозначно или страница отсутствует, компиляция завершится понятной ошибкой. LocLink является коротким псевдонимом LocalLink.

Ключевые кнопки в header

Для навигации верхнего уровня используйте HeaderButton. Локальная цель поддерживает те же варианты, что и LocalLink: объект функции страницы, номер, уникальный заголовок или заголовок внутри указанного раздела.

run(
    header_buttons=[
        # Короткая форма по функции страницы:
        HeaderButton("Знакомство", introduction_page),

        # Явная форма по номеру:
        HeaderButton.local("API reference", "3"),

        # По названию в определённом разделе:
        HeaderButton.local("Установка", "Install", section="2"),

        # Внешняя ссылка:
        HeaderButton.external("GitHub", "https://github.com/example/project"),
    ]
)

Локальная header-кнопка компилируется в обычный href="#/page/…", открывает страницу без перезагрузки и подсвечивается на всех вложенных страницах своего раздела. Старые словари остаются совместимыми:

header_buttons=[
    {"label": "Знакомство", "page": introduction_page},
    {"label": "API", "page": "API reference", "section": "3"},
    {"label": "GitHub", "href": "https://github.com/", "target": "_blank"},
]

Manifest

Manifest имеет версию схемы, заголовок, revision, updated_at, страницы, настройки UI и словарь ресурсов:

{
  "schema_version": 1,
  "title": "My docs",
  "revision": "…",
  "updated_at": "…",
  "pages": [{"number": "1", "title": "Home", "content": "<h1>…</h1>"}],
  "assets": {"photo.png": {"mime": "image/png", "data": "base64…"}},
  "ui": {"header_buttons": []}
}

При первом открытии встроенный manifest сохраняется в IndexedDB. Затем клиент один раз запрашивает manifest_path. Если revision или updated_at отличаются, новый manifest заменяет старый и страница перезагружается. При недоступном сервере продолжает работать кеш или встроенная копия.

Live-preview

В live-режиме браузер опрашивает только /live. Сервер следит за файлами, в которых объявлены страницы, и при изменении повторно выполняет их, после чего клиент получает новый manifest. В запускаемом файле вызов run() обязательно помещать под if __name__ == "__main__", чтобы watcher не запускал второй сервер.

HTTP-загрузка и исполнение произвольных .py файлов намеренно отсутствуют: такая ручка была бы удалённым выполнением кода. Live-preview работает с локальными исходниками проекта.

Свой клиент

Встроенные исходники находятся в docslib/templates/: index.html, client.css, client.js. Скопируйте папку, измените файлы и передайте template_dir="./my-template". Компилятор проверяет обязательные placeholders и всё равно выдаёт один автономный HTML.

Для тестов:

pytest