Skip to content

fix(image): markdown export of nested images + HTML round-trip (alignment, dimensions)#15

Merged
novincode merged 7 commits into
mainfrom
fix/image-markdown-html-roundtrip
Jun 14, 2026
Merged

fix(image): markdown export of nested images + HTML round-trip (alignment, dimensions)#15
novincode merged 7 commits into
mainfrom
fix/image-markdown-html-roundtrip

Conversation

@novincode

@novincode novincode commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Fixes #13, #10, #11. Also adds the npm release pipeline with provenance and a regression test suite.

Bug fixes (all root-caused in @lexkit/editor, with tests)

#13 — images skipped in markdown export

Image markdown only worked for top-level images. Lexical applies element transformers only to direct children of the root during export; an image nested inside a paragraph (where they land after HTML import or when placed inline with text) went through exportChildren, which only runs text-match transformers and otherwise falls back to a decorator's empty getTextContent() — so the image was silently dropped.

  • Added IMAGE_TEXT_MATCH_TRANSFORMER (text-match) alongside the existing element transformer, sharing one export/create-node implementation. Top-level images use the element transformer; nested/inline ones use text-match. No duplication.
  • Bonus: inline images can now also be imported from markdown (text ![](x) text).

#10 — width/height lost on HTML import

ImageTranslator.importDOM for img never read dimensions. It now reads width/height attributes and px inline styles (percentage/auto are ignored, since they can't be a numeric pixel size).

#11 — alignment reset to center on HTML round-trip

The figure conversion hardcoded "center". It now reads alignment back from the align-* class / text-align style the editor emits, plus dimensions/className/style from the inner <img>.

Also exported ImageNode, $createImageNode, $isImageNode, ImageTranslator, and both image transformers from the package root for discoverability (issue #13's reporter couldn't find the transformer).

Release pipeline

  • .github/workflows/release.yml: on push to main, builds + tests, and if packages/editor/package.json's version isn't on npm yet, publishes with provenance (id-token: write + NPM_CONFIG_PROVENANCE) and cuts a GitHub release. Requires the NPM_TOKEN repo secret.
  • .github/workflows/ci.yml: builds + tests on every PR/push.
  • files: ["dist"] + publishConfig.access: public for a clean, deterministic public package.
  • Bumped to 0.0.39.

Tests

15 vitest tests (jsdom) covering all three issues; both CI and release are gated on them.

novincode and others added 7 commits June 14, 2026 23:13
Image markdown export only worked for top-level images. Lexical applies
`element` transformers only to direct children of the root during export;
images nested inside another element (e.g. inside a paragraph after HTML
import or when placed inline with text) were routed through `exportChildren`,
which only runs `text-match` transformers and otherwise falls back to a
decorator's empty `getTextContent()` — so the image was silently dropped.

- Add IMAGE_TEXT_MATCH_TRANSFORMER (text-match) to handle nested/inline
  images, alongside the existing element transformer for block images.
- Share the export + node-creation logic between both transformers so output
  is identical regardless of tree position.
- Inline images can now also be imported from markdown (e.g. "text ![](x) text").
- Export ImageNode, $createImageNode, $isImageNode, ImageTranslator and the
  image transformers from the package root for discoverability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
)

ImageTranslator.importDOM dropped intrinsic dimensions and mis-read
alignment, so an HTML round-trip (getHTML -> injectHTML) lost image size
and reset alignment to center.

- #10: the `img` conversion now reads width/height from the `width`/`height`
  attributes and from `px` inline styles (percentage/auto are ignored as they
  can't be a numeric pixel size).
- #11: the `figure` conversion no longer hardcodes "center"; it reads the
  alignment back from the `align-*` class / `text-align` style the editor
  emits, and pulls dimensions, className and inline style from the inner img.
- Add shared extractAlignment() and extractDimension() helpers used by both
  the img and figure conversions.

Verified with a real-DOM (jsdom) export->serialize->parse->import round-trip
across left/center/right/none alignments and sized/unsized images.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add `files: ["dist"]` so the published tarball contents are explicit and
  deterministic (only the build output ships).
- Add `publishConfig.access: public` so the scoped package always publishes
  publicly.
- Bump version to 0.0.39 to ship the image markdown/HTML round-trip fixes
  (#10, #11, #13).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
release.yml publishes @lexkit/editor to npm whenever its version changes on
main: it builds, checks whether the version is already on npm, and if not
publishes with provenance (id-token: write) and cuts a GitHub release.
Requires an npm automation token in the `NPM_TOKEN` repo secret.

ci.yml builds the workspace on PRs and pushes to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ound-trip

Covers the three fixed issues so they can't silently regress:
- #13: markdown export of top-level, nested, and inline images; inline import.
- #10: width/height imported from img attributes and px inline styles.
- #11: alignment preserved (left/center/right/none) across an HTML round-trip.

Adds vitest + jsdom, a `test` script, and gates both CI and the release
workflow on the suite so a broken build can never be published.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NodeJS.Timeout requires @types/node, which isn't a dependency of this
browser library. It only resolved locally via hoisting and broke the DTS
build on a clean CI install (TS2694: Namespace 'NodeJS' has no exported
member 'Timeout'). ReturnType<typeof setTimeout> is environment-agnostic
and the correct type for a browser timer handle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@novincode novincode merged commit b229cc3 into main Jun 14, 2026
1 check passed
@novincode novincode deleted the fix/image-markdown-html-roundtrip branch June 14, 2026 19:52
novincode added a commit that referenced this pull request Jun 15, 2026
fix(image): markdown export of nested images + HTML round-trip (alignment, dimensions)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Image Markdown Transformer Not Working with Urls

1 participant