# dsh-docx-sidebar [简体中文](README.md) | [Français](README.fr.md) | [Deutsch](README.de.md) | [Italiano](README.it.md) | [Русский](README.ru.md) | [Español](README.es.md) Чтение `.docx` в правой боковой панели DSH (потребитель [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)): уровни заголовков, абзацы, отступы списков, **настоящие таблицы**, встроенные изображения — с защитой предохранителем. > **Это режим чтения, а не воспроизведение вёрстки.** Эта фраза прямо написана вверху интерфейса — без пагинации, без воспроизведения шрифтов, без обработки плавающих объектов, колонок и правок. Позволить пользователю считать отличия вёрстки багом хуже, чем честно обозначить границы возможностей. ## 1. Что умеет / чего не делает | Умеет | Описание | |------|------| | Уровни заголовков | Определяются **именем** стиля в `styles.xml` (`heading 1` → `w:h1`), а не угадыванием по конечной цифре id; `w:outlineLvl` как подстраховка | | Абзацы и строчное форматирование | Жирный, курсив, подчёркивание, `w:tab`/`w:br`, моноширинный (`rStyle`/`rFonts` даёт monospace) | | Списки | `w:numPr` + `w:ilvl` → уровни отступа (нумерационные последовательности не восстанавливаются) | | Таблицы | `w:tbl` → `w:tr` → `w:tc`, несколько абзацев в ячейке объединяются; **больше не дублируются как абзацы основного текста** | | Изображения | Байты извлекаются из архива → URL `blob:` (встроенные картинки лежат внутри zip, маршрута хоста для ссылки нет); EMF/WMF/TIFF отрисовать нельзя → помеченный плейсхолдер | | Верхние/нижние колонтитулы | Как помеченные note-блоки сверху/снизу | | Формулы | Не верстаются; показывается только текст, хранящийся в файле | **Не делает**: пагинация и поля, воспроизведение шрифтов и кеглей, плавающие/закреплённые объекты, колонки, правки (`w:ins`/`w:del`), примечания, восстановление нумерационных последовательностей, вёрстка формул. ## 2. Адаптация к ширине Панель боковой панели можно тянуть, поэтому режим чтения масштабируется вместе с шириной — **но с нижним и верхним пределами**; «адаптивность» — не разрешение увеличивать или уменьшать бесконечно: | Регулятор | Значение | Обоснование | |------|------|------| | Базовая ширина | 360px | При этой ширине коэффициент **равен ровно 1** — типичная ширина панели рендерит ровно тот макет, что в 0.1.0 | | Нижний предел | 0.9× | Даже уже — текст должен читаться | | Верхний предел | 1.15× | Шире — только комфорт, а не гигантизм | | Квантование | Два знака | Перетаскивание перевёрстывает шагами по 0,01, а не на каждый пиксель | Коэффициент выводится из ширины контейнера и записывается в CSS-переменную `--reader-scale`; базовый кегль — `13px × коэффициент`, основной текст целиком в `em`, поэтому текст **перенабирается** под новый кегль, а не масштабируется целиком через `transform` (иначе он стал бы мыльным). **Масштабируется только содержимое документа.** Заголовки, абзацы, списки, код, примечания, таблицы и подписи к изображениям следуют коэффициенту; **инфострока, элементы интерфейса кроме подписей колонтитулов, баннер предохранителя и состояния загрузки/пустоты держат фиксированный кегль** — меняющийся при перетаскивании разделителя текст интерфейса выглядел бы сбоем, а не адаптивностью. Два сопутствующих решения: - **Неизмеримая ширина откатывается к 1, а не к экстремуму.** В первом кадре ширина равна 0; рендер «как можно уже» дал бы вспышку микроскопического кегля при каждом открытии документа. - **Изображения в масштабировании не участвуют.** Показываются в заявленных документом размерах с потолком по ширине панели; растянуть растр сверх исходного размера в погоне за кеглем — только сильнее размыть, о воспроизведении речи нет. ## 3. Конвейер чтения ``` Байты архива (маршрут хоста /sidebar/file, бинарно) → центральный каталог (заявленный объём распаковки сверяется заранее) → потоковая распаковка + байтовый бюджет (счёт на ходу, при выходе за лимит — cancel) → word/document.xml блоки основного текста (последовательное сканирование, абзацы внутри таблиц пропускаются) → word/_rels/document.xml.rels rId → цели изображений / колонтитулов (External игнорируется) → word/styles.xml id стиля → уровень заголовка → word/media/* байты изображений (по требованию; сверх лимита — сразу пропуск, без распаковки) ``` Ноль рантайм-зависимостей: распаковка zip — через нативный для браузера `DecompressionStream('deflate-raw')`, XML — прицельным сканированием (WordprocessingML — ровная машинно-генерируемая структура, а регулярные выражения **не способны** балансировать одноимённые вложения, поэтому `findElements` считает глубину). ## 4. Предохранитель (семь контуров) | Измерение | По умолчанию | При превышении | |------|------|--------| | Размер архива | 5 MB | BLOCKED (не распаковано ничего) | | Суммарная распаковка | 64 MB | BLOCKED — защита от zip-бомб | | Одиночная часть | 32 MB | BLOCKED | | Число блоков | 5 000 | TRUNCATED, чтение останавливается | | Текст в блоке | 20 000 символов | блок опускается + предупреждение | | Число изображений | 200 | остальные пропускаются + предупреждение | | Одиночное изображение | 8 MB | изображение пропускается (**если объём уже превышен в каталоге, распаковки нет**) + предупреждение | > **Не более одного предупреждения на измерение** — структурная гарантия (внутри breaker строит Map по `reason`), а не договорённость: дважды сработавшее измерение нарисовало бы на баннере две почти одинаковые строки — братский плагин однажды выпустил патч-версию именно из-за этого. Результат срабатывания предохранителя — полноправный гражданин: BLOCKED рендерится читаемой карточкой ошибки, без исключений и без бесконечного ожидания. ## 5. Сборка и проверка ```bash npm run build # сдвоенная точка входа esbuild + tsc-декларации типов; при сборке bundle один раз по-настоящему загружается через node:vm с проверкой apply/inject npm test # 14 проверочных утверждений npm run verify # build && test ``` Тестовая фикстура — **настоящий docx-zip, собранный на месте** (корректные CRC32 и центральный каталог, ссылка на настоящий PNG 1×1); покрывает: определение заголовков по имени стиля (`List1` не должен распознаваться ошибочно), строчное форматирование, уровни списков, таблицы без дублирования ячеек абзацами, EMU→px, плейсхолдеры неотрисовываемых форматов, note колонтитулов и игнорирование связей External, самосогласованность счётчиков, а также пять контуров и целостность плейсхолдеров zh/en. ## 6. Установка ```bash # Официальный CLI (рекомендуется; заодно ведёт зависимости и dsh.profile.bundles) dsh plugin --profile add > ``` Ручная установка — три шага, все обязательны (только шаги 1 и 3 = плагин никогда не загрузится и не сообщит об ошибке): ① скопировать в `node_modules/` профиля; ② добавить `dsh-docx-sidebar` в `dsh.profile.bundles` в `package.json` профиля; ③ дописать строку insert из `cordis.patch.yml` этого пакета в `cordis.patch.yml` профиля. После установки сначала офлайн-проверка, затем перезапуск: ```powershell dsh --profile --dump-config | Select-String dsh-docx-sidebar ``` **Зависимость**: `dsh-better-sidebar >= 0.18.1` (опциональна). При её отсутствии плагин грузится нормально, warn в консоли и не добавляет ни одной записи. **Матрица совместимости**: | Версия плагина | Диапазон хостов DSH | Примечание | |---------|-------------|------| | 0.3.0 | `>=0.2.0-rc.1 <0.2.1-0` | Линия 0.2.0 (`main`, повышена из `compat/0.2.0`). Чисто метаданная адаптация: потребляемая поверхность — сплошь чистые caller-ы `ctx.get(...)`; 0.2.0-rc.1 полностью совместим с плагинным API 0.1.7 | | 0.2.0 | `>=0.1.5-rc.1 <0.2.0-0` | Обслуживается замороженными ветками `compat/0.1.7` / `compat/0.1.5` | `engines.dsh` в `package.json`, peer `@deepseek-ai/dsh-client-locale` и `engines.dsh` в `dsh.plugin.json` — три места, один диапазон, держатся синхронно. ## 7. Отношения с `dsh-opensheet-sidebar` Таблицы (csv/xlsx) и документы (docx) — **два плагина, разделённые по доменам способностей**, каждый самодостаточен: без import между плагинами — это создало бы жёсткую связанность между двумя плагинами «мягкой зависимости» и ловушки порядка установки. Общий код zip/бюджета распаковки каждый держит своей копией; о выделении общего пакета подумают, когда появится **третий** потребитель (решение по порогу, а не упреждающее проектирование). ## 多语言说明 / Sprachen / Langues / Языки / Idiomas / Lingue Настоящий README написан по-китайски. Кратко об установке и совместимости (эта линия требует DSH 0.2.0: `>=0.2.0-rc.1 <0.2.1-0`; установка: `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`): - **Deutsch** — benötigt DSH 0.2.0 (`>=0.2.0-rc.1 <0.2.1-0`), getestet gegen DSH 0.2.0-rc.1. Installation: `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`. Die 0.1.x-Wirtslinie wird von den eingefrorenen Zweigen `compat/0.1.7` / `compat/0.1.5` (npm-Tags `dsh-0.1.7` / `dsh-0.1.5`) versorgt. - **Français** — nécessite DSH 0.2.0 (`>=0.2.0-rc.1 <0.2.1-0`), testé avec DSH 0.2.0-rc.1. Installation : `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`. La lignée d'hôtes 0.1.x est assurée par les branches figées `compat/0.1.7` / `compat/0.1.5` (tags npm `dsh-0.1.7` / `dsh-0.1.5`). - **Русский** — требуется DSH 0.2.0 (`>=0.2.0-rc.1 <0.2.1-0`), протестировано на DSH 0.2.0-rc.1. Установка: `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`. Линия хостов 0.1.x обслуживается замороженными ветками `compat/0.1.7` / `compat/0.1.5` (npm-теги `dsh-0.1.7` / `dsh-0.1.5`). - **Español** — requiere DSH 0.2.0 (`>=0.2.0-rc.1 <0.2.1-0`), probado con DSH 0.2.0-rc.1. Instalación: `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`. La línea de anfitriones 0.1.x la atienden las ramas congeladas `compat/0.1.7` / `compat/0.1.5` (etiquetas npm `dsh-0.1.7` / `dsh-0.1.5`). - **Italiano** — richiede DSH 0.2.0 (`>=0.2.0-rc.1 <0.2.1-0`), testato su DSH 0.2.0-rc.1. Installazione: `dsh plugin --profile add dsh-docx-sidebar@dsh-0.2.0`. La linea di host 0.1.x è servita dai rami congelati `compat/0.1.7` / `compat/0.1.5` (tag npm `dsh-0.1.7` / `dsh-0.1.5`).