Add "Rendering"

2026-04-12 20:46:37 +00:00
parent dc07929326
commit a8508ff587

127
Rendering.md Normal file

@@ -0,0 +1,127 @@
# Рендер и постобработка
## Сборка PUG-кода
Функция `rebuildOutput()` конкатенирует блоки:
```
//Заголовок ← комментарий с именем блока
+spacerLine(40) ← спейсер (если addSpacing)
tr
td.paddingWrapper
span.font.h2 ТЕКСТ
//БАННЕР
+spacerLine(20)
tr
td
a(href="https://...")
img(src="https://...")
```
Если сертификат включён (`certificateEnabled`), блоки сертификата добавляются в конец.
## Render Cache
- **Ключ**: MD5 от `slug + pug + gender + genderPaths`
- **Хранение**: Map в памяти + `render-cache.json` на диске
- **Размер**: LRU, максимум 30 записей
- **Инвалидация**: при изменении любого параметра рендера
## Preheader
### Формат
```
{текст прехедера} <vk-snippet-end>&#10240;×130
```
- Текст прехедера — основное содержимое
- `<vk-snippet-end>` — тег для Mail.ru (snippet boundary)
- `&#10240;×130` — невидимые символы-пустышки, чтобы почтовые клиенты не показывали текст письма после прехедера
### Цепочка обработки
1. В Pug: `+preheader("текст")` (миксин из mixins.pug)
2. Миксин генерирует `#MAILRU_PREHEADER_TAG#` в HTML
3. server.js заменяет `#MAILRU_PREHEADER_TAG#``<vk-snippet-end/>`
### Санитизация preheader (защита от инъекций)
```javascript
preheader.replace(/[\r\n`\\]/g, '').replace(/"/g, '\\"')
```
Убираются переносы строк (предотвращает выход из строки `+preheader("...")`), backtick и backslash.
## Nowrap (висячие предлоги)
Функция `applyNowrap(html)` — оборачивает короткие предлоги/союзы с следующим словом в `<span style="white-space:nowrap">`.
### Алгоритм
1. Находит только `<span>` элементы с классом `h3`, за которыми `</span></td>`
2. Для каждого совпадения вызывает `wrapShort(text)`:
- Regex: `(?<![a-zA-Zа-яА-ЯёЁ])([a-zA-Zа-яА-ЯёЁ]{1,3})(?:\s|&nbsp;)+(\S+)`
- Слово 1-3 буквы + пробел + следующее слово
3. Placeholder-подход: сначала вставляет `\u200B` (zero-width space), затем заменяет на `<span style="white-space:nowrap">...\u00A0...</span>`
4. Placeholder нужен чтобы не было двойной обработки
### Пример
```
Идеи на каждый день → Идеи <span style="white-space:nowrap">на каждый</span> день
```
## Mindbox Tag Processing
Функция `processMindboxTags(html, feedUrl)`:
### Что обрабатывает
1. **Рекомендации** — удаляет `@{for...}@{end for}` блоки
2. **Переменные** — резолвит `@{set var = value}`
3. **Условия**:
- `@{if ...DiscountPercent > 0}` — скрывает блок если нет скидки
- `@{if ...OldPrice > ...Price}` — скрывает если нет реальной скидки
- Вложенные условия поддерживаются
4. **Подстановки**:
- `${Products...GetByValue('name')}` → название товара из фида
- `${formatDecimal(price)}` → форматированная цена с пробелами
- `${ResizeImage(url, width, height)}` → URL картинки
5. **Бейджи** — если товар не в наличии, на картинку накладывается полупрозрачный оверлей "Нет в наличии"
### Маппинг свойств Mindbox → Фид
| Mindbox | Фид |
|---------|-----|
| `name` | name |
| `vendorName` | vendor |
| `url` | url |
| `pictureUrl` | image |
| `price` | price |
| `oldPrice` | oldPrice |
| `description` | description |
| `customField.vendorCode` | vendorCode |
| `customField.discountPercent` | discountPercent |
| и другие... | |
## Concurrency Limit
Максимум 3 параллельных рендера (`MAX_CONCURRENT_RENDERS`).
При превышении — HTTP 429 "Слишком много параллельных рендеров, подождите".
Счётчик `activeRenders` обёрнут в `try/finally` для гарантии декремента.
## Preview в iframe
```html
<iframe sandbox="allow-same-origin" srcdoc={previewHtml} />
```
- `sandbox="allow-same-origin"` — блокирует выполнение скриптов (XSS), но позволяет доступ к DOM
- `fitPreviewFrame()` — масштабирует содержимое под viewport
- Zoom: 40-100%, сохраняется в профиле на сервере
- Click detection: через `contentDocument.addEventListener` (не инъекция скрипта)
## Quick Edit
Клик по элементу в превью → находит соответствующий блок и поле → показывает плавающий редактор.
1. `injectPreviewClickDetection()` — навешивает click listener на iframe DOM
2. `handlePreviewElementClick({text, rect})` — ищет блок по тексту (normalizeMatchText)
3. `openQuickEdit(block, field, iframeRect)` — позиционирует редактор у элемента
4. Изменения сразу применяются к блоку, помечают превью как stale