feat(editor): inline image alignment — place several images side by side #284

Merged
vvzvlad merged 1 commits from image-inline-row into develop 2026-07-02 14:12:05 +03:00
Owner

What

A new "Inline (side by side)" alignment mode for images: a sixth value inline of the existing image align attribute. Consecutive inline images render as inline-block containers, forming a row that wraps naturally on narrow viewports. Unlike the float modes (#145), text does not wrap around them.

How

  • applyAlignment (editor-ext): reset-then-apply extended to display/vertical-align; the reset restores the constructor's inline display: flex, so non-inline modes carry byte-identical inline styles as before, and editor-ext stays independent of the client CSS class.
  • Image bubble menu: new button (IconLayoutColumns) after the float buttons, active-state via align: inline.
  • i18n: key registered in en-US and ru-RU («В ряд»), mirroring the float labels.
  • No MCP/markdown/git-sync changes needed: align already round-trips losslessly as a plain string via data-align (verified in review).

Out of scope

  • Group centering / adjustable gaps (would require a wrapper gallery node — rejected in favor of the lightweight attr approach, approved by the owner).
  • React fallback node view (placeholders): intentionally ignores inline, same as the float modes.

Verification

  • pnpm --filter @docmost/editor-ext test: 26 files / 247 tests passed (3 new applyAlignment specs: apply, reset-on-switch-away, floatLeft→inline).
  • tsc --noEmit on client: 0 errors.
  • Two review passes: initial (APPROVE WITH SUGGESTIONS → i18n key + comment accuracy fixes) and focused re-review (APPROVE, no findings).

Note: pushed via owner transport — the claude_code Gitea account from AGENTS.md no longer exists (API: "user redirect does not exist"); commits are still authored by claude_code.

## What A new "Inline (side by side)" alignment mode for images: a sixth value `inline` of the existing image `align` attribute. Consecutive inline images render as `inline-block` containers, forming a row that wraps naturally on narrow viewports. Unlike the float modes (#145), text does not wrap around them. ## How - `applyAlignment` (editor-ext): reset-then-apply extended to `display`/`vertical-align`; the reset restores the constructor's inline `display: flex`, so non-inline modes carry byte-identical inline styles as before, and editor-ext stays independent of the client CSS class. - Image bubble menu: new button (IconLayoutColumns) after the float buttons, active-state via `align: inline`. - i18n: key registered in en-US and ru-RU («В ряд»), mirroring the float labels. - No MCP/markdown/git-sync changes needed: `align` already round-trips losslessly as a plain string via `data-align` (verified in review). ## Out of scope - Group centering / adjustable gaps (would require a wrapper gallery node — rejected in favor of the lightweight attr approach, approved by the owner). - React fallback node view (placeholders): intentionally ignores inline, same as the float modes. ## Verification - `pnpm --filter @docmost/editor-ext test`: 26 files / 247 tests passed (3 new applyAlignment specs: apply, reset-on-switch-away, floatLeft→inline). - `tsc --noEmit` on client: 0 errors. - Two review passes: initial (APPROVE WITH SUGGESTIONS → i18n key + comment accuracy fixes) and focused re-review (APPROVE, no findings). Note: pushed via owner transport — the `claude_code` Gitea account from AGENTS.md no longer exists (API: "user redirect does not exist"); commits are still authored by claude_code.
vvzvlad added 1 commit 2026-07-02 04:37:43 +03:00
Add a new value "inline" to the image align attribute (alongside
left/center/right/floatLeft/floatRight). Inline images render as
inline-block containers, so consecutive ones form a row that wraps
naturally on narrow viewports; unlike the float modes, text does not
wrap around them.

- applyAlignment: reset-then-apply extended to display/vertical-align;
  the reset restores the constructor's inline display:flex so non-inline
  modes keep byte-identical styles and editor-ext stays independent of
  the client CSS class
- image bubble menu: new "Inline (side by side)" button (IconLayoutColumns)
  with active state, mirroring the float buttons
- i18n: key registered in en-US and ru-RU ("В ряд"), like the float labels
- tests: 3 new applyAlignment specs (apply, reset on switch-away, float->inline)
- no schema/MCP/markdown changes needed: align round-trips as data-align
vvzvlad added the review/needs label 2026-07-02 05:14:41 +03:00
Collaborator

Ревью 20032be92 — inline-выравнивание картинок (side by side). Полный 9-аспектный веер (отдельный субагент на каждый).

Вердикт: PASS. Аккуратная фича; и — важно — корректно обходится БЕЗ правок схемных копий. Готово к мержу.

Что проверено

  • Три копии схемы (готча) — обойдено верно. inline — новое ЗНАЧЕНИЕ существующего атрибута align, не новый mark/attr. Проверил: mcp И git-sync сериализуют align value-agnostic (data-align="${escapeAttr(attrs.align)}", parseHTML: getAttribute("data-align") — без enum/whitelist), поэтому inline round-trip'ится лосслесс через ОБЕ копии без единой правки. applyAlignment (NodeView-стайлинг) живёт только в editor-ext — это рендер, не схема, зеркалить не нужно. CHANGELOG-заявление о лосслесс round-trip как data-align — верное.
  • reset-then-apply (stability/regressions): reset теперь дополнительно ставит display:flex (дефолт конструктора ResizableNodeView) и чистит verticalAlign, затем inlineinline-block+verticalAlign:top+padding-gap. Для 5 существующих режимов (left/center/right/floatLeft/floatRight) стили байт-идентичны прежним (конструктор и так ставил flex); переключение inline↔другой не течёт (float/display/vertical-align чистятся). 3 новых non-vacuous теста (inline→стили+no-float; inline→center восстанавливает flex; floatLeft→inline чистит float).
  • security (align — фиксированный enum с кнопки, только литеральные style-строки, escapeAttr на сериализации — нет XSS) / conventions (кнопка зеркалит соседние align-кнопки; тип setImageAlign расширен; i18n оба локаля; CHANGELOG) / coherence (клик→align:inline→inline-block, active-state, round-trip) — LGTM.

Объективные проверки (в окружении ревью)

  • editor-ext: pnpm --filter @docmost/editor-ext test247 passed (вкл. 3 новых image.spec); tsc --noEmit -p . (клиент, после build editor-ext) — exit 0. eslint в окружении не поднялся → базис eslint = кодер + вычитка.

Маркер reviewed_head20032be92.

Ревью **20032be92** — inline-выравнивание картинок (side by side). Полный 9-аспектный веер (отдельный субагент на каждый). **Вердикт: PASS.** Аккуратная фича; и — важно — корректно обходится БЕЗ правок схемных копий. Готово к мержу. ### Что проверено - **Три копии схемы (готча) — обойдено верно.** `inline` — новое ЗНАЧЕНИЕ существующего атрибута `align`, не новый mark/attr. Проверил: mcp И git-sync сериализуют align value-agnostic (`data-align="${escapeAttr(attrs.align)}"`, `parseHTML: getAttribute("data-align")` — без enum/whitelist), поэтому `inline` round-trip'ится лосслесс через ОБЕ копии без единой правки. `applyAlignment` (NodeView-стайлинг) живёт только в editor-ext — это рендер, не схема, зеркалить не нужно. CHANGELOG-заявление о лосслесс round-trip как `data-align` — верное. - **reset-then-apply (stability/regressions):** reset теперь дополнительно ставит `display:flex` (дефолт конструктора ResizableNodeView) и чистит `verticalAlign`, затем `inline` → `inline-block`+`verticalAlign:top`+padding-gap. Для 5 существующих режимов (left/center/right/floatLeft/floatRight) стили байт-идентичны прежним (конструктор и так ставил flex); переключение inline↔другой не течёт (float/display/vertical-align чистятся). 3 новых non-vacuous теста (inline→стили+no-float; inline→center восстанавливает flex; floatLeft→inline чистит float). - security (align — фиксированный enum с кнопки, только литеральные style-строки, escapeAttr на сериализации — нет XSS) / conventions (кнопка зеркалит соседние align-кнопки; тип `setImageAlign` расширен; i18n оба локаля; CHANGELOG) / coherence (клик→align:inline→inline-block, active-state, round-trip) — LGTM. ### Объективные проверки (в окружении ревью) - editor-ext: `pnpm --filter @docmost/editor-ext test` — **247 passed** (вкл. 3 новых image.spec); `tsc --noEmit -p .` (клиент, после build editor-ext) — **exit 0**. eslint в окружении не поднялся → базис eslint = кодер + вычитка. Маркер `reviewed_head` — `20032be92`. <!-- state:review reviewed_head=20032be92 round=1 verdict=approved -->
agent_reviewer added review/approved and removed review/needs labels 2026-07-02 05:43:27 +03:00
vvzvlad merged commit 3a5794894e into develop 2026-07-02 14:12:05 +03:00
Sign in to join this conversation.