SharedDocsLib/README.md

179 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 10 секунд; при вводе поле раскрывает ранжированные результаты вниз. Кнопка `⟳` вращается во время проверки manifest; при наведении она заменяет поиск и тему, показывая ISO-дату и короткий `revision`, а при нажатии синхронизирует manifest и перезагружает страницу.
На мобильных устройствах используется viewport `100dvh` и нижний dock. Кнопка `☰` заменяет содержимое страницы копией desktop-sidebar. Ввод в нижний поиск заменяет содержимое результатами, расположенными снизу вверх по релевантности. Тема и обновление на мобильном скрыты.
## Установка из Git
```bash
uv venv
source .venv/bin/activate
uv pip install "git+https://git.snw.su/snw/SharedDocsLib.git@main"
```
Для разработки и запуска примера из клонированного репозитория:
```bash
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`)
## Минимальный пример
```python
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, но в новом коде лучше использовать правильное имя.
## Компоненты
Доступны `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. Поэтому ссылку можно копировать, открывать в новой вкладке и активировать с клавиатуры. Клиент перехватывает только обычный клик и показывает нужную страницу без перезагрузки.
```python
# По объекту функции страницы — предпочтительный вариант:
LocalLink("Установка", install_page)
# По точному номеру:
LocalLink("Установка", "1.2")
# По уникальному названию:
LocalLink("Установка", "Install")
# По названию внутри раздела, если названия повторяются:
LocalLink("Установка для сервера", "Install", section="2")
```
Цель по функции разрешается во время компиляции, когда все `@Page` уже зарегистрированы, поэтому можно ссылаться и на функцию, объявленную ниже по файлу. Если название неоднозначно или страница отсутствует, компиляция завершится понятной ошибкой. `LocLink` является коротким псевдонимом `LocalLink`.
### Ключевые кнопки в header
Для навигации верхнего уровня используйте `HeaderButton`. Локальная цель поддерживает те же варианты, что и `LocalLink`: объект функции страницы, номер, уникальный заголовок или заголовок внутри указанного раздела.
```python
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/…"`, открывает страницу без перезагрузки и подсвечивается на всех вложенных страницах своего раздела. Старые словари остаются совместимыми:
```python
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 и словарь ресурсов:
```json
{
"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.
Для тестов:
```bash
pytest
```