You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Following up from #403, where @hryhoriiK97 suggested contributing this. The contributing guide asks for a discussion before implementation PRs, so here's the design I have in mind. I'd like to agree on the approach before writing code.
Why
The renderer handles  on both platforms, and the core parser already emits image nodes. The input drops them. On iOS, ENRMInputParser doesn't list MD_SPAN_IMG. On Android, InputParser maps NodeType.Image to nothing. The README lists inline images in the input under future plans, so this seems like the right next gap to close.
Our use case is a chat composer where custom emoji render as small inline images while you type. Inline images are the priority for us, and I'd propose keeping block images out of the first pass to keep the PRs small.
Proposed design
The input models text as formatting ranges (characters) and block ranges (lines). An image is neither. It's one atomic character (U+FFFC) that carries a url and alt text. Instead of a parallel store like #403's ENRMImageStore, I'd put that payload on the range itself, so images ride the same adjust-for-edit path as everything else and there's no second store to keep ordered.
Per area:
Parser. iOS: add MD_SPAN_IMG and teach the raw-to-plain mapping about substituted characters (plain text stops being a strict subsequence of the source once U+FFFC replaces the image syntax). Android: handle NodeType.Image in InputParser.walkNode, append U+FFFC, record the range.
Atomicity. Caret and selection behavior copied from links: the caret can't land inside an image, backspace deletes it whole, selection snaps around it.
Rendering. Reuse the renderer's pipeline. ENRMImageAttachment on iOS, which needs a small refactor since it takes the renderer's StyleConfig today. ImageSpan on Android, which needs the MarkdownSpan marker plus URL-keyed instance reuse so a restamp doesn't re-download. Async height resolution hooks the existing layout observer into the input's height update path.
Serializer. Images fit neither the paired-delimiter path nor the line-prefix path, so both serializers get a third mode: replace the U+FFFC at that position with .
Style props.markdownStyle.image and markdownStyle.inlineImage on the input, same keys as the renderer.
Commands.insertImage(url, alt?) following the insertLink shape. Once the parser knows images, insertText('') works too.
Tests and docs. Maestro flows under .maestro/enrichedMarkdownInput with screenshot baselines for both platforms, plus INPUT.md and API_REFERENCE.md updates.
Questions before I start
feat(ios): add editable markdown input with editor controller #628 adds an editable input with image attachments to the standalone iOS package. Is the RN input going to move onto that editor? If so I'd rather build against it than add this to the current stack twice.
PR split: I'd do the TS spec plus iOS first, with Android right behind it. If you'd rather have platform parity in every PR, I can structure it that way instead.
Is an inline-only first pass fine, with block images as a follow-up?
Plan
I'd start with the round trip and skip rendering entirely. Once the parser and serializer both understand images, setValue() then getMarkdown() should hand back  untouched, with a placeholder glyph standing in for the image. That's the riskiest code on both platforms, so I'd want it reviewed before building any UI on top of it.
After that, roughly in order: attachment rendering and the style props (async height included), the insertImage command with the example app and Storybook coverage, the editing behavior (atomic caret, backspace, copy and paste), and the Maestro flows, screenshots, and docs last.
Happy to split these into separate PRs so no single review gets big.
@hryhoriiK97, (and anyone else) let me know if you have any feedback. Thanks!
Following up from #403, where @hryhoriiK97 suggested contributing this. The contributing guide asks for a discussion before implementation PRs, so here's the design I have in mind. I'd like to agree on the approach before writing code.
Why
The renderer handles
on both platforms, and the core parser already emits image nodes. The input drops them. On iOS,ENRMInputParserdoesn't listMD_SPAN_IMG. On Android,InputParsermapsNodeType.Imageto nothing. The README lists inline images in the input under future plans, so this seems like the right next gap to close.Our use case is a chat composer where custom emoji render as small inline images while you type. Inline images are the priority for us, and I'd propose keeping block images out of the first pass to keep the PRs small.
Proposed design
The input models text as formatting ranges (characters) and block ranges (lines). An image is neither. It's one atomic character (U+FFFC) that carries a url and alt text. Instead of a parallel store like #403's
ENRMImageStore, I'd put that payload on the range itself, so images ride the same adjust-for-edit path as everything else and there's no second store to keep ordered.Per area:
MD_SPAN_IMGand teach the raw-to-plain mapping about substituted characters (plain text stops being a strict subsequence of the source once U+FFFC replaces the image syntax). Android: handleNodeType.ImageinInputParser.walkNode, append U+FFFC, record the range.ENRMImageAttachmenton iOS, which needs a small refactor since it takes the renderer'sStyleConfigtoday.ImageSpanon Android, which needs theMarkdownSpanmarker plus URL-keyed instance reuse so a restamp doesn't re-download. Async height resolution hooks the existing layout observer into the input's height update path..markdownStyle.imageandmarkdownStyle.inlineImageon the input, same keys as the renderer.insertImage(url, alt?)following theinsertLinkshape. Once the parser knows images,insertText('')works too..maestro/enrichedMarkdownInputwith screenshot baselines for both platforms, plusINPUT.mdandAPI_REFERENCE.mdupdates.Questions before I start
Plan
I'd start with the round trip and skip rendering entirely. Once the parser and serializer both understand images,
setValue()thengetMarkdown()should hand backuntouched, with a placeholder glyph standing in for the image. That's the riskiest code on both platforms, so I'd want it reviewed before building any UI on top of it.After that, roughly in order: attachment rendering and the style props (async height included), the
insertImagecommand with the example app and Storybook coverage, the editing behavior (atomic caret, backspace, copy and paste), and the Maestro flows, screenshots, and docs last.Happy to split these into separate PRs so no single review gets big.
@hryhoriiK97, (and anyone else) let me know if you have any feedback. Thanks!